Servlet框架基础和生命周期(结合源码)、destroy()的思考
前言
Servlet是一个java编写的程序,此程序是在服务器端运行的,是按照Servlet规范编写的一个
java类。Servlet是处理客户端的请求,并将处理结果以响应的方式返回给客户端。Servlet框架
是怎样的呢?它的生命周期又是什么情况呢?这是本文需要探求的。
Servlet框架
网上下载Servlet源码,解压之后发现其由两个包组成:
1、javax.servlet
2、javax.servlet.http
javax.servlet
此包中定义了所有Servlet类都必须实现的接口或类。
接口定义:
ServletConfig接口---在初始化过程中由Servlet容器(Tomcat调用)
ServletContext接口---定义Servlet用于获取容器信息的方法
ServletRequest接口---向服务器请求信息
ServletResponse接口 ---响应客户端请求
Servlet接口---定义所有的Servlet必须实现的方法
类定义:
ServletInputStream类 --- 用于从客户端读取二进制数据
ServletOutputStream类 ---用于将二进制数据写入到客户端
GenricServlet--- 抽象类,定义一个通用的,独立于底层协议的servlet。
java.servlet.http
此包中定义了使用HTTP通信协议的所有Servlet类应该实现的类、接口。
接口定义:
HttpServletRequest接口 --- 封装http请求
HttpServletResponse接口 --- 封装http响应
HttpSession接口 --- 用于表示客户端存储有关客户的信息
HttpSessionAttributeListener接口---实现这个监听接口,当用户获取Session的属性列表发生
改变的时候得到通知。
类的定义:
HttpServlet类 --- 扩展了GenericServlet的抽象类
Cookie类 --- 创建一个Cookie,Cookie技术,用户存储服务器发送给客户端的信息。
通过阅读Servlet框架源码,其主要的框架结构如下图:
Servlet工作过程
通过上述Servlet框架的了解我们可以初步描述一下Servlet在Tomcat容器中是如何工作的。
来看下面的时序图:
1、Web Client 向Servlet容器(Tomcat)发出Http请求
2、Servlet容器接收Web Client的请求
3、Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中
4、Servlet容器创建一个HttpResponse对象
5、Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse
对象作为参数传给 HttpServlet对象
6、HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息
7、HttpServlet调用HttpResponse对象的有关方法,生成响应数据
8、Servlet容器把HttpServlet的响应结果传给Web Client
Tomcat和HttpServlet是如何进行交互的呢?从源码中我们可以得到
Servlet生命周期
在Servlet框架中所有的Servlet类都必须实现Servlet这个接口。其中定义了三个方法:
1、init方法:负责初始化Servlet对象。
2、service方法:用于响应客户端的请求
3、destroy:销毁Servlet对象,释放占用的资源。
Servlet生命周期四个阶段:
● 加载阶段:加载并实例化(创建Servlet实例)
● 初始化阶段:调用init()方法
● 响应客户请求阶段:调用service()方法,doGet、doPost
● 终止阶段:调用destroy()方法
加载阶段
Tomcat从文件系统,远程文件系统或其他网络服务中通过类加载器来加载Servlet,并调用
Servlet的默认构造方法(不带参构造器)
初始化阶段init()方法
当Servlet容器启动时:读取web.xml配置文件中的信息,构造指定的Servlet对象,根据配置
文件的信息创建ServletConfig对象,并将其作为参数传递给init方法进行调用。
Tomcat启动后:用户首次想某个Servlet对象发送请求,Tomcat会判断内存中是否存在指定的
servlet对象,如果没有则会去创建它,然后创建HttpRequest,HttpResponse对象,调用service
方法处理用户的请求。
从Servlet的构造开始我们没有显示的看到init()方法的调用,那么init方法到底是何时进行调用
的呢?阅读源码可以知道:init方法是在实例化Servlet之后调用的,其参数ServletConfig是在
Servlet初始化阶段Tomcat根据web.xml配置信息,和操作系统的相关环境生成并传递给init
方法的。
[java] * Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service.
*
* <p>The servlet container calls the <code>init</code>
* method exactly once after instantiating the servlet.
* The <code>init</code> method must complete successfully
* before the servlet can receive any requests.
*
* <p>The servlet container cannot place the servlet into service
* if the <code>init</code> method
* <ol>
* <li>Throws a <code>ServletException</code>
* <li>Does not return within a time period defined by the Web server
* Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service.
*
* <p>The servlet container calls the <code>init</code>
* method exactly once after instantiating the servlet.
* The <code>init</code> method must complete successfully
* before the servlet can receive any requests.
*
* <p>The servlet container cannot place the servlet into service
* if the <code>init</code> method
* <ol>
* <li>Throws a <code>ServletException</code>
* <li>Does not return within a time period defined by the Web server
[java] **
*
* A servlet configuration object used by a servlet container
* to pass information to a servlet during initialization.
*
*/
public interface ServletConfig {
/**
*
* A servlet configuration object used by a servlet container
* to pass information to a servlet during initialization.
*
*/
public interface ServletConfig {
响应客户请求阶段service方法
service()方法是在客户端第一次访问servlet时执行的,其实init方法同样也是在有客户端访问
servlet的时候才被调用。不过需要特别注意的是讨论init方法在session级别上时,当存在不同的
会话访问相同的servlet时,Tomcat会开启一个线程处理这个新的会话,但是此时Tomcat容器
不会实例化这个servlet对象,也就是有多个线程在共享这个servlet实例。换句话说Servlet对象在
servlet容器中是以单例的形式存在的!然而查看其源码可以发现,Servlet在多线程下并未使用同
步机制,因此,在并发编程下servlet是线程不安全的。
对于Servlet的并发,线程安全的处理问题,笔者会找个时间好好的整理下思路。
对于不同的session访问相同的serlvet对象,只有一次init的过程,笔者会在接下来予以演示。
阅读HttpServlet的源码可以知道,基于Http通信协议的HttpServlet在进行客户端响应处理的
时候根据客户端请求,响应的类别不同分别调用不同的方法,其中最常用的就是doGet、doPost
方法,这两个方法是我们在编写Servlet中的主要的逻辑处理阶段。
[java] 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);
}
}
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);
}
}
终止阶段:destroy()方法的调用
上面的探讨中知道的了Servlet是如何加载、初始化、处理客户端的请求响应的,那么Servlet
在什么时候终止呢?其生命周期又是在什么时候结束的呢?
我们知道的是Servlet生命周期是有Tomcat容器来管理的,由此在Tomcat关闭、或者Restart
的时候,servlet的生命周期必然结束,destroy方法也必然被调用过。在客户端与服务器的一次
Session会话中,session关闭之后servlet并未销毁。后续演示。
总的来说servlet对象什么时候destroy的呢?
1、Tomcat服务器stop
2、web项目reload
3、Tomcat容器所在的服务器shutdown(这不废话吗?)
更正之处:对于destroy()方法笔者略有疑惑,“它到底是如何销毁Servlet的呢?”,基于这个问题
特意的去查看了源码,结果发现destroy()方法在Servlet框架中并未具体去实现。它是由Coder
自己去实现的。因此“destroy()方法用户销毁Servlet”这种说法本身就是离谱的!查阅源码:
[java]
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. See {@link Servlet#destroy}.
*
*
*/
/**
*
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. This method is
* only called once all threads within the servlet's
* <code>service</code> method have exited or after a timeout
* period has passed. After the servlet container calls this
* method, it will not call the <code>service</code> method again
* on this servlet.
*
* <p>This method gives the servlet an opportunity
* to clean up any resources that are being held (for example, memory,
* file handles, threads) and make sure that any persistent state is
* synchronized with the servlet's current state in memory.
*
*/
public void destroy();
}
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. See {@link Servlet#destroy}.
*
*
*/
/**
*
* Called by the servlet container to indicate to a servlet that the
* servlet is being taken out of service. This method is
* only called once all threads within the servlet's
* <code>service</code> method have exited or after a timeout
* period has passed. After the servlet container calls this
* method, it will not call the <code>service</code> method again
* on this servlet.
*
* <p>This method gives the servlet an opportunity
* to clean up any resources that are being held (for example, memory,
* file handles, threads) and make sure that any persistent state is
* synchronized with the servlet's current state in memory.
*
*/
public void destroy();
}
阅读上述注释,很明白的是destroy的调用是表明Servlet结束其servcie阶段,destroy方法的调用
实际是在servlet销毁之前,由Tomcat来调用的,其作用是清理一些资源的占用情况,例如文件、
线程,而且确保任何持久的状态和servlet的当前状态在内存中是同步的。
不过destroy的调用情况上述的总结是正确的。
也就是说Servlet的销毁时destroy()一定会被掉用,servlet方法基本是由Tomcat回调的!
但是destroy()方法的调用只是回收一些资源,并不意味着Servlet已经销毁。至于何时销毁,这个
笔者也不太明了,希望有人可以指出,不过据源码是Servlet结束servcie服务时销毁,Tomcat关闭
时也会销毁。(皮之不存毛将安附焉?)
简单的测试下:
我们在doPost()方法里面简单的调用下destory方法,run项目,之后另起一个Session访问
输出情况:
这就说明了destroy()和Servlet的销毁不存在必然联系,只是在Servlet销毁之前,destroy方法,会
基由Tomcat回调,进行一些资源的清理,文件关闭。
Servlet生命周期演示
为了更进一步的了解Servlet生命周期(它确实十分重要),笔者新建一个简单的web项目予以说明
不当之处请指正,一起交流。
Eclipse配合Tomcat如何新建一个web项目,这个笔者就不必多说了吧,挺简单的,网上的总结
各式各样的也不少。不过需要注意的是高版本的Eclipse若果读者不注意的话产生的web项目是没有
web.xml文件的,以注解的形式代替了。
runtime选择自己配置好的Tomcat容器,web Module version选择2.5就可以了,不要选择3.0
这里笔者贴出项目中的一些歌主要的文件。
web.xml配置文件。
[html] <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>Servlet02</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>HelloServlet</display-name>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.kiritor.servlet.HelloServlet</servlet-class>
<init-param>
<description></description>
<param-name>info</param-name>
<param-value>this is a init message</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>Servlet02</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>HelloServlet</display-name>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.kiritor.servlet.HelloServlet</servlet-class>
<init-param>
<description></description>
<param-name>info</param-name>
<param-value>this is a init message</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/HelloServlet</url-pattern>
</servlet-mapping>
</web-app> 注意servlet映射的配置,以及初始化参数的配置。
自定义Servlet的代码:
[java] package com.kiritor.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public HelloServlet() {
super();
}
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
super.init(config);
System.out.println("init方法被执行");
System.out.println("相关的初始化参数:");
System.out.println(config.getInitParameter("info"));
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
printWriter.write("Hello world");
}
@Override
protected void service(HttpServletRequest arg0, HttpServletResponse arg1)
throws ServletException, IOException {
super.service(arg0, arg1);
System.out.println("service方法被执行");
}
@Override
public void destroy() {
super.destroy();
System.out.println("destroy方法被执行");
}
}
package com.kiritor.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public HelloServlet() {
super();
}
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
super.init(config);
System.out.println("init方法被执行");
System.out.println("相关的初始化参数:");
System.out.println(config.getInitParameter("info"));
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
printWriter.write("Hello world");
}
@Override
protected void service(HttpServletRequest arg0, HttpServletResponse arg1)
throws ServletException, IOException {
super.service(arg0, arg1);
System.out.println("service方法被执行");
}
@Override
public void destroy() {
super.destroy();
System.out.println("destroy方法被执行");
}
}
接下来我们直接run该项目。看看后台输出结果,现在貌似可以直接在Eclispe控制台查看
输出信息了,十分方便。
init方法是在servlet实例化后由Tomcat容器进行调用的,生成了诸多信息,其中包含我们自己
定义的Servlet配置信息,在init方法中我们通过ServletConfig对象获取到了。
之后service方法执行了。这里新开启一个session会话,看看情况。图我就不贴了,控制台
多输出一句service被执行。证明了servlet在容器中实例单例的形式存在。源码层面上Servlet不是
单例的,只是由于容器对其的维护,使之产生了类似单例的效果。
接下来我们看destroy方法的调用情况,只是针对上述两种情况来说的,不可能笔者还去关机‘
演示。首先我们关闭Tomcat服务器
上一篇: Object-C,文件路径API
下一篇: 香椿要焯水吗,想吃好的你就得知道这些