Servlet生命周期以及工作原理

  最近感觉到用久了SpringMVC、Struts2等框架,反而对它们的底层实现,即Servlet,的相关知识有了许多遗忘。现在参考了网上的一些博客,来进行一次知识点总结。   

Servlet响应客户端请求的过程

image_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命周期

  1. init方法:当Servlet容器第一次加载并创建Servlet实例时,在调用该Servlet的构造函数后立即调用init方法对该Servlet对象进行初始化。构造器和init方法都只会被调用一次,这说明Servlet是单实例的(需要考虑线程安全的问题,不推荐在其中写全局变量)。
  2. service方法:每次请求都会调用service方法,它是实际用于响应请求的方法,可以被多次调用。
  3. destroy方法:在服务器端停止且卸载Servlet时执行该方法,用于释放当前Servlet所占用的资源,只会被调用一次。

以上方法都由Servlet容器(例如Tomcat)负责调用。    

ServletConfig

  Servlet接口的init方法会接收一个ServletConfig对象作为参数,即init(ServletConfig servletConfig),ServletConfig对象封装了该Serlvet的配置信息,并且可以获取ServletContext对象。例如:
  
在web.xml中配置Servlet的初始化参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet>
<!-- Servlet注册的名字 -->
<servlet-name>helloServlet</servlet-name>
<!-- Servlet全类名 -->
<servlet-class>servlet.HelloServlet</servlet-class>
<!-- 配置该Servlet的初始化参数 -->
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>

可以在HelloServlet的init方法中获取这些参数的信息(在实际开发中,通常使用的Servlet都继承了HttpServlet类,可以通过getServletConfig()在该Servlet对象中获取到ServletConfig对象,不一定只能在init方法中使用,下面的getServletContext()也是一样):

1
2
3
4
5
6
7
8
9
10
11
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取Servlet初始化参数相关的信息
String username = servletConfig.getInitParameter("username");
System.out.println("username is "+username);
Enumeration<String> names = servletConfig.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
System.out.println("name is "+name);
System.out.println("value is "+servletConfig.getInitParameter(name));
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB    

ServletContext

  ServletContext对象代表着当前的WEB应用。可以认为SerlvetContext是当前 WEB应用的一个大管家,可以从中获取到当前WEB应用的各个方面的信息。
  ServletContext对象可以由SerlvetConfig获取:

1
ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 获取当前WEB应用的初始化参数:
  
在web.xml中配置当前WEB应用的初始化参数:(这些参数可以被所有的 Servlet通过ServletContext获取,而上面配置在Servlet中的参数只能由对应的Servlet获取)

1
2
3
4
5
6
7
8
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<context-param>
<param-name>jdbcUrl</param-name>
<param-value>jdbc:mysql:///xiangwanpeng</param-value>
</context-param>

在init方法中获取到这些参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取ServletContext对象,它包含了当前WEB应用的各种信息
ServletContext servletContext = servletConfig.getServletContext();
//获取WEB应用的初始化信息,方法和servletConfig类似
String driver = servletContext.getInitParameter("driver");
System.out.println("driver is "+driver);
Enumeration<String> names2 = servletContext.getInitParameterNames();
while(names2.hasMoreElements()){
String name2 = names2.nextElement();
System.out.println("name2 is "+name2);
System.out.println("value2 is "+servletContext.getInitParameter(name2));
}
}

当有请求发送到该Servlet后可以在控制台看到输出:

image_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 获取当前WEB应用的某一个文件在服务器上的绝对路径,而不是部署前的路径。

例如现在在根目录下新建一个note.txt:

image_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中获取:

1
2
String realPath = servletContext.getRealPath("/note.txt");
System.out.println("realPath is "+realPath);

获取请求后在控制台输出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 获取当前 WEB应用的名称:

1
2
String contextPath = servletContext.getContextPath();
System.out.println(contextPath);

④ 获取当前 WEB应用的某一个文件对应的输入流:

在src目录下新建一个jdbc.properties:

image_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中获取该文件的输入流:

1
2
InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");
System.out.println("is is "+is);

输出:

image_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意区别另一种方法,即通过getClassLoader获取,这种方法的路径是没有/WEB-INF/classes/前缀的:

1
2
InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
System.out.println("is2 is "+is2);

⑤ 和attribute相关的几个方法。    

GenericServlet

  GenericServlet是Servlet接口和ServletConfig接口的实现类,是一个抽象类,因为其中的service()方法为抽象方法。它的作用是:如果新建的 Servlet程序直接继承GenericSerlvet会使开发更简洁。
  
具体实现:
① 在GenericServlet中声明了一个SerlvetConfig类型的成员变量,在init(ServletConfig)方法中对其进行了初始化。
② 利用servletConfig成员变量的方法实现了 ServletConfig接口的方法。
③ 还定义了一个 init()方法,在init(SerlvetConfig)方法中对其进行调用,子类可以直接覆盖init()在其中实现对Servlet的初始化。
④ 不建议直接覆盖 init(ServletConfig),因为如果忘记编写super.init(config),而还是用了SerlvetConfig接口的方法,则会出现空指针异常。
⑤ 新建的 init(){}并非Serlvet的生命周期方法,而init(ServletConfig)是生命周期相关的方法。

以下是源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
public GenericServlet() { }
public void destroy() {
}
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
getServletContext().log(getServletName() + ": "+ msg);
}
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletName() {
return config.getServletName();
}
}

     

HttpServlet

  HttpServlet是一个继承自 GenericServlet的Servlet,而且它是针对于 HTTP协议所定制。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse强制转换为HttpServletRequest和HttpServletResponse。并调用了重载的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通过request.getMethod()获取请求方式,并根据请求方式创建了doXxx()方法(xxx为具体的请求方式,比如 doGet,doPost)。
  实际开发中, 我们通常直接继承HttpServlet,这样做的好处是可以直接有针对性的覆盖doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要进行类型的强制转换。
  
主要源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}