servlet生命周期

本主题由 wzzz 创建于 2009-10-20 16:32:20

servlet 有良好的生存期的定义,包括如何加载、实例化、初始化、处理客户端请求以及如何被移除。这个生存期由 javax.servlet.Servlet 接口的 init,service 和 destroy 方法表达。

   1 、加载和实例化
  容器负责加载和实例化一个 servlet 。实例化和加载可以发生在引擎启动的时候,也可以推迟到容器需要该 servlet 为客户请求服务的时候。
   首先容器必须先定位 servlet 类,在必要的情况下,容器使用通常的 Java 类加载工具加载该 servlet ,可能是从本机文件系统,也可以是从远程 文件系统甚至其它的网络服务。容器加载 servlet 类以后,它会实例化该类的一个实例。需要注意的是可能会实例化多个实例,例如一个 servlet 类因 为有不同的初始参数而有多个定义,或者 servlet 实现 SingleThreadModel 而导致容器为之生成一个实例池。

   2 、初始化
    servlet 加载并实例化后,容器必须在它能够处理客户端请求前初始化它。初始化的过程主要是读取永久的配置信息,昂贵资源(例如 JDBC 连接)以及 其它仅仅需要执行一次的任务。通过调用它的 init 方法并给它传递唯一的一个(每个 servlet 定义一个) ServletConfig 对象完成这个过 程。给它传递的这个配置对象允许 servlet 访问容器的配置信息中的名称-值对( name-value )初始化参数。这个配置对象同时给 servlet 提供了访问实现了 ServletContext 接口的具体对象的方法,该对象描述了 servlet 的运行环境。

   2.1 初始化的错误处理
   在初始化期间, servlet 实例可能通过抛出 UnavailableException 或者 ServletException 异常表明它不能进行有效服务。如果一个 servlet 抛出一个这样的异常,它将不会被置入有效服务并且应该被容器立即释 放。在此情况下 destroy 方法不会被调用因为初始化没有成功完成。在失败的实例被释放后,容器可能在任何时候实例化一个新的实例,对这个规则的唯一例 外是如果失败的 servlet 抛出的异常是 UnavailableException 并且该异常指出了最小的无效时间,那么容器就会至少等待该时间指明的 时限才会重新试图创建一个新的实例。

   2.2 、工具因素
  当工具(注:根据笔者的理解,这个工具可能是应用服务器的 某些检查工具,通常是验证应用的合法性和完整性)加载和内省( introspect )一个 web 应用时,它可能加载和内省该应用中的类,这个行为将触发那 些类的静态初始方法被执行,因此,开发者不能假定只要当 servlet 的 init 方法被调用后它才处于活动容器运行状态( active container runtime )。作为一个例子,这意味着 servlet 不能在它的静态(类)初始化方法被调用时试图建立数据库连接或者连接 EJB 容器。

   3 、处理请求
   在 servlet 被适当地初始化后,容器就可以使用它去处理请求了。每一个请求由 ServletRequest 类型的对象代表,而 servlet 使用 ServletResponse 回应该请求。这些对象被作为 service 方法的参数传递给 servlet 。在 HTTP 请求的情况下,容器必须提供代表请 求和回应的 HttpServletRequest 和 HttpServletResponse 的具体实现。需要注意的是容器可能会创建一个 servlet 实 例并将之放入等待服务的状态,但是这个实例在它的生存期中可能根本没有处理过任何请求。

   3.1 、多线程问题
  容器 可能同时将多个客户端的请求发送给一个实例的 service 方法,这也就意味着开发者必须确保编写的 servlet 可以处理并发问题。如果开发者想防止这 种缺省的行为,那么他可以让他编写的 servlet 实现 SingleThreadModel 。实现这个类可以保证一次只会有一个线程在执行 service 方法并且一次性执行完。容器可以通过将请求排队或者维护一个 servlet 实例池满足这一点。如果 servlet 是分布式应用的一部分,那么,那么容器可 能在该应用分布的每个 JVM 中都维护一个实例池。如果开发者使用 synchronized 关键字定义 service 方法 ( 或者是 doGet 和 doPost) ,容器将排队处理请求,这是由底层的 java 运行时系统要求的。我们强烈推荐开发者不要同步 service 方法或者 HTTPServlet 的诸如 doGet 和 doPost 这样的服务方法。

   3.2 、处理请求中的异常
   servlet 在对请求进行服务的时 候有可能抛出 ServletException 或者 UnavailableException 异常。 ServletException 表明在处理请求的过 程中发生了错误容器应该使用合适的方法清除该请求。 UnavailableException 表明 servlet 不能对请求进行处理,可能是暂时的,也可 能是永久的。如果 UnavailableException 指明是永久性的,那么容器必须将 servlet 从服务中移除,调用它的 destroy 方法并释 放它的实例。如果指明是暂时的,那么容器可以选择在异常信息里面指明的这个暂时无法服务的时间段里面不向它发送任何请求。在这个时间段里面被被拒绝的请求 必须使用 SERVICE_UNAVAILABLE (503) 返回状态进行响应并且应该携带稍后重试( Retry-After )的响应头表明不能服务只是暂时的。容器也可以选择不对暂时性和永久性的不可用 进行区分而全部当作永久性的并移除抛出异常的 servlet 。

   3.3 线程安全
  开发者应该注意容器实现的请求和响 应对象(注:即容器实现的 HttpServletRequest 和 HttpServletResponese )没有被保证是线程安全的,这就意味着他们只 能在请求处理线程的范围内被使用,这些对象不能被其它执行线程所引用,因为引用的行为是不确定的。

   4 、服务结束
   容器没有被要求将一个加载的 servlet 保存多长时间,因此一个 servlet 实例可能只在容器中存活了几毫秒,当然也可能是其它更长的任意时间(但是 肯定会短于容器的生存期)当容器决定将之移除时(原因可能是保存内存资源或者自己被关闭),那么它必须允许 servlet 释放它正在使用的任何资源并保存 任何永久状态(这个过程通过调用 destroy 方法达到)。容器在能够调用 destroy 方法前,它必须允许那些正在 service 方法中执行的线程执行 完或者在服务器定义的一段时间内执行(这个时间段在容器调用 destroy 之前)。一旦 destroy 方法被调用,容器就不会再向该实例发送任何请求。如 果容器需要再使用该 servlet ,它必须创建新的实例。 destroy 方法完成后,容器必须释放 servlet 实例以便它能够被垃圾回收。


从 Tomcat 处理用户请求,我们可以清晰的看到容器 Servlet 的生命周期管理过程:

1 、客户发出请求 —>Web 服务器转发到 Web 容器 Tomcat ;

2 、 Tomcat 主线程对转发来用户的请求做出响应创建两个对象: HttpServletRequest 和 HttpServletResponse ;

3 、从请求中的 URL 中找到正确 Servlet , Tomcat 为其创建或者分配一个线程,同时把 2 创建的两个对象传递给该线程;

4 、 Tomcat 调用 Servlet 的 servic() 方法,根据请求参数的不同调用 doGet() 或者 doPost() 方法;

5 、假设是 HTTP GET 请求, doGet() 方法生成静态页面,并组合到响应对象里;

6 、 Servlet 线程结束, Tomcat 将响应对象转换为 HTTP 响应发回给客户,同时删除请求和响应对象。

从该过程中,我们可以理解 Servlet 的生命周期: Servlet 类加载(对应 1 步); Servlet 实例化(对应 2 步);调用 init 方法(对应 3 步);调用 service() 方法(对应 4 、 5 步);调用 destroy() 方法(对应 6 步)。

放心注册,GeeKaa不会虚耗你半点时间,你只会发现更多乐趣。 立即注册