servlet
注意: 现在默认是在类上写注解@WebServlet("/Aservlet")来代替web.xml的配置
servlet就是在服务器端接受请求并完成响应的java类。
第一个例子 -- Hello World
使用到了MyEclipse与Tomcat7。新建一个Web Project。
新建一个Java Class,如下
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class Aservlet implements Servlet {
@Override
public void destroy() {
System.out.println("servelet调用了destroy()");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
System.out.println("servelet服务器init");
}
@Override
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
System.out.println("servelet服务器接收到请求");
resp.getWriter().write("Hello World");
}
}
在web.xml里面注册。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<!-- 注册servlet到服务器 -->
<servlet>
<!-- 配置servlet的名字,保证唯一,一般用类名 -->
<servlet-name>Aservlet</servlet-name>
<!-- 完整路径,若没有package,就是如下写法 -->
<servlet-class>Aservlet</servlet-class>
</servlet>
<!-- 配置servlet的访问路径 -->
<servlet-mapping>
<!-- 这个名字和上面的name一致 -->
<servlet-name>Aservlet</servlet-name>
<url-pattern>/Aservlet</url-pattern>
</servlet-mapping>
<!-- 默认使用index.jsp作为欢迎文件 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在浏览器输入http://localhost:8080/hello/Aservlet
就可以看到显示Hello World.
servlet生命周期
- Servlet对象创建时机? 第一次访问servlet时。
- Servlet对象创建的特点? 只在第一次访问时调用
init
,一个servlet实例在服务器中只有一个。 - 当请求访问servlet时,
service
方法会处理请求. - 当服务器将要关闭,服务器会销毁服务器中的Servlet对象,在真正销毁之前调用
destory
方法。
其他方法
-
getServletInfo
=> 基本用不到,可以返回servlet作者信息、版权等。 -
getServletConfig
=> 获得启动信息对象,如下面的例子
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
// 这里获得的config是从init里面得到的,需要用一个成员变量存储。扩大其生命周期
@Override
public ServletConfig getServletConfig() {
return config;
}
ServletConfig详解
-
String getInitParameter(String name)
获得配置信息 根据键获得值 -
Enumeration getInitParameterNames()
获得配置信息 获得所有键 -
String getServletName()
获得servlet的名称 <servlet-name>AServlet</servlet-name> -
ServletContext getServletContext()
该方法返回ServletContext对象.
在目录下新建一个Bservlet
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class Bservlet implements Servlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public void destroy() {
System.out.println("servelet调用了destroy()");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
// 获得所有参数建
Enumeration<String> en = getServletConfig().getInitParameterNames();
while (en.hasMoreElements()) {
// 获得键
String key = en.nextElement();
// 获得值
String value = getServletConfig().getInitParameter(key);
resp.getWriter().write(key + " -> " + value + "\n");
}
// 获得servlet名字
String servletName = getServletConfig().getServletName();
resp.getWriter().write(servletName+ "\n");
}
}
在web.xml
里新增
<!-- 其他内容 -->
<servlet>
<!-- 配置servlet的名字,保证唯一,一般用类名 -->
<servlet-name>Bservlet</servlet-name>
<!-- 完整路径,若没有package,就是如下写法 -->
<servlet-class>Bservlet</servlet-class>
<!-- 配置初始化参数,键值对的形式
getInitParameterNames()获取的就是这里所有的参数键值对-->
<init-param>
<param-name>name</param-name>
<param-value>Tom</param-value>
</init-param>
<init-param>
<param-name>age</param-name>
<param-value>18</param-value>
</init-param>
</servlet>
<!-- 配置servlet的访问路径 -->
<servlet-mapping>
<!-- 这个名字和上面的name一致 -->
<servlet-name>Bservlet</servlet-name>
<url-pattern>/Bservlet</url-pattern>
</servlet-mapping>
<!-- 其他内容 -->
在浏览器输入http://localhost:8080/hello/Bsevlet
,显示如下数据
name -> Tom
age -> 18
Bservlet
GenericServlet
自己写一个MyGenericServlet
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public abstract class MyGenericServlet implements Servlet, ServletConfig {
private ServletConfig config;
@Override
public void destroy() {
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return "";
}
// 在连接服务器的时候,init(config)肯定会被调用,如果想在初始化动作里面完成其他初始化,再写一个空参init被有参的调用
// 子类重写空参init即可
// 这么做把默认初始化和用户自定义初始化绑定起来了
@Override
public void init(ServletConfig config) throws ServletException {
// init方法 妥善的保存config对象
this.config = config;
System.out.println("有参init");
this.init();
}
// 自定义初始化
// 空参init方法,为了防止开发人员重写 原生init方法
public void init() throws ServletException {
}
// 将service函数设为抽象的,强迫用户去实现。因为没有service函数的servlet是没有意义的
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
@Override
public String getServletName() {
return config.getServletName();
}
@Override
public ServletContext getServletContext() {
return config.getServletContext();
}
@Override
public String getInitParameter(String key) {
return config.getInitParameter(key);
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
新建一个类继承刚写的MyGenericServlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
// 只是重写了init()和service方法
public class Cservlet extends MyGenericServlet {
// 连接到服务器会自动调用有参的init,打印有参init和无参init
@Override
public void init() {
System.out.println("无参init");
}
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
String value = getInitParameter("name");
String servletName = getServletName();
res.getWriter().write(value + "\n");
res.getWriter().write(servletName + "\n");
}
}
GenericServlet有如下好处
1. init方法 妥善的保存config对象
2. 空参init方法,为了防止开发人员重写 原生init方法
3. service方法空实现=> 声明为抽象
4. destory方法空实现
5. 实现getServletInfo,getServletConfig
6. 实现了servletConfig接口. 接口中的方法直接调用config实现类实现.
HttpServlet
自己实现一个简单的MyHttpservlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 这里声明为abstract主要是为了防止用户直接使用该类(new一个实例)。而必须通过继承
public abstract class MyHttpServlet extends MyGenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
// 帮用户强转
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// 函数重载, 用户自定义的
service(req, res);
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得请求方式,根据请求方式的不同调用不同的doXXX()方法
String method = request.getMethod();
if ("GET".equals(method)) {
doGet(request, response);
} else if ("POST".equals(method)) {
doPost(request, response);
}
}
// 处理GET请求
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 具体的处理逻辑
}
// 处理POST请求
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 具体的处理逻辑
}
}
子类继承MyHttpServlet后,可以选择实现doGet()
或者doPost()
。也可以重写自定义的service(HttpServletRequest req, HttpServletResponse res)
,这样不会太注重到底是什么请求方法。
servlet细节
servlet线程安全
Servlet的实例在服务器运行期间只有一个实例存在,所以线程不安全。
线程不安全: 如果使用成员变量来接受线程参数,如果发生并发,那么会出现线程问题(覆盖,实例只有一个,成员变量也只有一个,后面来的会把前面的覆盖)
解决办法: 将装载线程参数的变量放置到方法中,写成局部变量。
servlet的创建实例时机
默认情况: 第一次访问该servlet时候。
让servlet实例随着服务器的启动而创建,在servlert
标签中 添加一个配置即可:<load-on-startup></load-on-startup>
在该配置中填入一个整数可实现。
数字的数值,在有多个servlet需要随着服务器启动而启动时,决定启动顺序。数字越小优先级越高。最小就是0。一般0~5。取3就行。
如果数字一样,谁先配置谁先创建。
servlet的路径配置
<url-pattern></url-pattern>
该配置,配置方式有两种:
- 路径匹配: 一定以"/"开头
/AServlet
-> http://localhost:8080/project-name/Aservlet
/ABC/AServlet -> http://localhost:8080/project-name/ABC/Aservlet
/ABC/* -> http://localhost:8080/project-name/ABC/any-word
*匹配任意内容太
- 后缀名匹配: 以开头*
*.do
*.action
*.html
后缀名匹配和路径匹配不能同一配置中混合使用. 例如: /.do
一个servlet可以配置多个路径. 直接在<servlet-mapping>元素中添加多个<url-pattern>配置即可。优先级: /AServlet > /abc/ > *.do > /*
注意:匹配范围越大,优先级越低。
ServletContext
我们一个Web项目有且只有一个ServletContext .
- 创建:随着项目的启动而创建
- 销毁:随着项目的关闭而销毁
- 获得:通过ServletConfig对象的 getServletContext方法获得。
功能
- 可以获得项目参数
- 是Servlet技术中的3个域对象之一
- 获得项目内的资源
<!-- 项目级的启动参数 -->
<context-param>
<param-name>name</param-name>
<param-value>Rose</param-value>
</context-param>
<context-param>
<param-name>age</param-name>
<param-value>15</param-value>
</context-param>
<context-param>
<param-name>gender</param-name>
<param-value>female</param-value>
</context-param>
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Fservlet extends HttpServlet {
// 获得并输出所有项目启动参数
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得ServletContext对象
ServletContext servletContext = getServletContext();
Enumeration<String> en = servletContext.getInitParameterNames();
while (en.hasMoreElements()) {
String key = en.nextElement();
String value = servletContext.getInitParameter(key);
response.getWriter().write("<h2>"+key + " -> " + value+"</h2>");
}
}
}
servlet之间通讯
先写一个简单的例子,用另外一个类作为中间媒介,通过static对象在类之间共享,实现通讯。
import java.util.HashMap;
import java.util.Map;
public class Constant {
public static String word;
public static Map<String, Object> map = new HashMap<>();
}
Gservlet向Hservlet发送消息
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Gservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 通过static对象在类之间共享
Constant.word = "hahaha";
Constant.map.put("money", "1000");
Constant.map.put("iphone", "Apple");
}
}
Hservlet接收消息
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Hservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String word = Constant.word;
String money = Constant.map.get("money");
String phone = Constant.map.get("iphone");
System.out.println(word);
System.out.println(money);
System.out.println(phone);
}
}
其实ServletContext已经帮我们想到了这一点
使用servletContext.setAttribute(key, value)设置属性键值对。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Gservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext sc = getServletContext();
sc.setAttribute("money", "100");
sc.setAttribute("iphone", "Apple");
System.out.println("已发送");
// Constant.word = "hahaha";
// Constant.map.put("money", "1000");
// Constant.map.put("iphone", "Apple");
}
}
使用servletContext.setAttribute(key, value)设置属性键值对。
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Hservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// String word = Constant.word;
// String money = Constant.map.get("money");
// String phone = Constant.map.get("iphone");
ServletContext sc = getServletContext();
String money = (String) sc.getAttribute("money");
String phone = (String) sc.getAttribute("iphone");
System.out.println(money);
System.out.println(phone);
}
}
其他方法
// 删除键值对
sc.removeAttribute("money");
// 遍历所有属性
Enumeration<String> en = sc.getAttributeNames();
while (en.hasMoreElements()) {
String key = en.nextElement();
System.out.println(key);
}
上面的ServletContext介绍的是application域。
Servlet三大域
- application
- request
- session
域用于服务器组件之间的通讯(例如:两个servlet之间通讯)。域的实质就是map。application域 就是在整个项目内共享数据的map,所以两个servlet之间通信,其他servlet也会知道。且application域不是线程安全的,不能知道接受到的信息是否就是来自某一个具体的servlet,例如上例中Hservlet不知道它接收到信息就是Gservlet传来的。
操作域的方法:
void setAttribute(String key,Object value);
Object getAttribute(String key);
Enumeration<String> getAttributeNames();
void removeAttribute(String key);
获得内部资源
package servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URL;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Iservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext sc = getServletContext();
// 1、 获得WebRoot下的user.xml这样写就行
InputStream is = sc.getResourceAsStream("/user.xml");
System.out.println(is);
String realPath = sc.getRealPath("/user.xml");
System.out.println(realPath); // //\webapps\cookie_session\user.xml
// 2、Class也有获得资源的方法,/表示根目录是src或者说classes目录,ServletContext的根目录是项目的根目录(WebRoot)
// 这样写是获取classes目录下的xml,WEB-INF/classes/user.xml
InputStream is3 = Fservlet.class.getResourceAsStream("/user.xml");
System.out.println(is3);
// 这样写是获取工程包(package)下的目录下的xml,注意不加"/"
InputStream is4 = Fservlet.class.getResourceAsStream("user.xml");
System.out.println(is4);
// 获得classes下具体某个包下xml的绝对路径
URL url = Fservlet.class.getResource("user.xml");
System.out.println(url.getPath()); // /webapps/cookie_session/WEB-INF/classes/cookie/user.xml
// 获得src(或者说classes)下xml的绝对路径
URL url2 = Fservlet.class.getResource("/user.xml");
System.out.println(url2.getPath()); // /webapps/cookie_session/WEB-INF/classes/user.xml
}
}
总结
Servlet Server Applet -> 运行在服务器端的小程序
功能:接收请求并完成响应。
本质:就是一个Java类
规则
- 实现Servlet
- 继承GenericServlet
- 继承HttpServlet
生命周期
- 第一次访问时,服务器会创建servlet的实例,创建完后才调用init ()方法进行初始化。
- 讲请求交给
service(req, res)
处理。 - 服务器关闭时调用destroy()方法。
servlet具体过程
小任务--统计访问人数
package servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Lservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1 获得Application域中存放的统计数字
Integer count = (Integer) getServletContext().getAttribute("count");
//2 判断是否获得到统计数字
if(count == null){
//没获得到=> 将数字初始化为1
count = 1;
}else{
//获得到了=> 将数字加1
count += 1;
}
//3 输出,放回到Application域中
response.getWriter().write("you are the "+ count+" vistors");
getServletContext().setAttribute("count",count);
}
}
by @sunhaiyu
2017.3.25