3. Servlet接口
Servlet接口是Servlet API的核心抽象,所有的servlet都需要实现这个接口。可以直接实现servlet接口,而更常见的方式则是通过扩展/继承一个实现了servlet接口的类来实现这一点。在API中提供两个实现了servlet接口的类:GenericServlet和HttpServlet。大部分情况下,开发人员通过继承HttpServlet来实现自己的servlet。
3.1. 处理请求的方法(Request Handling Methods)
Servlet接口定义了一个名为service的方法来处理客户端的请求。每当servlet容器将request路由到一个servlet实例时,这个方法都会被调用。多个request线程可以同时调用并执行同一个service方法。
3.1.1. HTTP专用的请求处理方法(HTTP Specific Request Handling Methods)
HttpServlet抽象子类添加了几个附加的方法,service方法会根据request自动调用这些附加的方法。这些方法是:
l doGet方法,用于处理HTTP GET请求
l doPost方法,用于处理HTTP POST请求
l doPut方法,用于处理HTTP PUT请求
l doDelete方法,用于处理HTTP DELETE请求
l doHead方法,用于处理HTTP HEAD请求
l doOptions方法,用于处理HTTP OPTIONS请求
l doTrace方法,用于处理HTTP TRACE请求
在开发基于HTTP的servlet的时候,开发人员一般只涉及doGet和doPost方法。其他的方法可视为高级方法,为熟悉HTTP编程的程序员准备。
Servlet开发人员可以通过实现doPut和doDelete方法来支持HTTP/1.1客户端。HttpServlet的doHead方法会执行doGet方法,但是只返回doGet方法生产的头信息给客户端。doOptions方法自动检测servlet支持哪些HTTP方法,并将此信息发送给客户端。doTrace方法则将返回一个response,其中包含了trace 请求中的所有头信息。
由于HTTP/1.0没有定义PUT, DELETE, OPTIONS 和TRACE方法,因此,只支持HTTP/1.0的容器,只会使用servlet的doGet, doHead和doPost。
3.1.2. 有条件GET的支持(Conditional GET Support)
HttpServlet接口定义了getLastModified方法用于支持有条件get操作。有条件get操作是指客户端通过HTTP GET方法请求一个资源的时候,在头信息里设置只有当被请求的资源在指定时间之后被修改过,才返回响应的body部分。
Servlets that implement the doGet method and that provide content that does not necessarily change from request to request should implement this method to aid in efficient utilization of network resources.
3.2. 实例数量
缺省情况下,在容器中,每个servlet定义(servlet definition)只有一个实例。
在servlet实现了SingleThreadModel接口的情况下,容器将创建多个实例,这样容器能够处理高负载请求,并同时保证request排队访问单个servlet实例。
对于一个标记为可分布的应用,对于容器所使用的每一个虚拟机,都将创建为一个servlet定义的创建一个实例。如果servlet实现了SingleThreadModel方法,则对容器使用的每个虚拟机,都可以创建多个实例。
3.2.1. 关于单线程模式(SingleThreadModel)的说明
SingleThreadModel接口用于保证在同一时刻,只有一个线程,访问一个servlet实例的service方法。需要重点说明的是,上述保证只限于对servlet实例的访问。对于能被多个servlet实例访问的对象,例如HttpSession的实例对象,还是能够被多个servlet实例同时访问的,不管servlet是否实现SingleThreadModel接口。
3.3. Servlet生命周期
Servlet生命周期管理包括:如何被载入(load),如何实例化,如何初始化,如何处理客户端情况,以及如何被销毁。这个生命周期,在API中,通过Servlet接口(javax.servlet.Servlet)的init,service和destroy方法得以体现。所有的servlet都必须实现这些接口。可以直接实现servlet接口,也可以通过继承GenericServlet或HttpServlet抽象类来实现。
3.3.1. 载入和实例化
Servlet容器负责servlet的载入和实例化。这项工作可以在servlet引擎启动的时候进行,也可以延迟到当容器检测到需要servlet来处理某个请求的时候进行。
首先,servlet容器得能找到servlet对应的class文件。容器可以根据需要选择使用java类装载器(class loader)从本地文件系统、远程文件系统或者网络服务中加载这个类。
在这个类被加载之后,容器就创建该类的一个实例。
需要重点说明的是,在servlet容器中,一个servlet类可能会有多个实例。例如,多个servlet定义使用同一个类,但定义了不同的初始化参数。另外,如果servlet实现了SingleThreadModel接口,容器会创建一个实例池。
3.3.2. 初始化
在servlet对象载入和实例化之后,容器必须在servlet处理客户端请求之前对它进行初始化。在初始化过程中,servlet可以读取持久化的配置数据,初始化昂贵(costly)的资源,例如jdbc数据库连接,并执行其他一次性的操作。容器通过调用Servlet接口的init来初始化servlet,对于每个servlet定义,容器将创建一个唯一的、实现了ServletConfig接口的对象,并将其作为参数传递给init方法。通过这个配置信息对象,servlet可以name-value的方式获取初始化参数。这个配置信息对象还包含了一个实现了ServletContext接口的对象,描述了servlet的运行环境信息。关于ServletContext接口的详细信息请看第4章。
3.3.2.1. 初始化过程出错
在初始化过程中,servlet实例可以通过抛出UnavailableException或者ServletException异常来声明初始化失败。这种情况下,容器必须释放servlet实例,不能将其作为一个活动的服务。由于初始化没有成功,此时destroy方法不会被调用。
在初始化失败的servlet实例被释放之后,容器可以在任何时候实例化并初始化新的servlet。唯一的例外是在servlet初始化过程中抛出的UnavailableException中定义了最短失效时间,在这段时间内,不能创建新的实例。
3.3.2.2. Tool Considerations
用工具加载并分析(introspect)一个web应用的时候,它可以加载并分析(introspect)web应用的成员类,这会触发静态初始化方法的执行。出于这个特性的考虑,在Servlet接口的init方法被调用之前,开发人员不能认为servlet已经在活动在容器的运行环境内。例如,当servlet的静态初始化方法被调用的时候,不应当去创建到数据库或者EJB容器的连接
3.3.3. 请求处理
Servlet被正确初始化之后,容器就能用它来处理请求。请求被封装在ServletRequest类型的对象中,响应信息被封装在ServletResponse类型的对象中,这两个对象以参数的形式传给Servlet接口的service方法。处理HTTP请求时,容器必须通过实现HttpServletRequest和HttpServletResponse接口来提供请求和响应对象。
需要说明的是,servlet实例被创建成为服务之后,可能在整个生命周期不需要响应任何请求。
3.3.3.1. 多线程问题
在处理客户端情况过程中,容器有可能并发访问servlet的service方法,以处理来自客户端的并发访问。开发人员必须注意这一点,保证servlet在并发情况下正确运行。
开发人员可以通过让servlet实现SingleThreadModel接口来避免这种缺省行为。通过实现这个接口可以保证同一时刻只允许一个请求线程调用service方法。Servlet容器可以多种方式实现这个要求,可以让访问一个servlet的请求排队,也可以提供servlet实例池。如果servlet属于可分布应用,容器可以在应用所分布的每个虚拟机中保持一个servlet实例池。
如果service方法使用了synchronized修饰符(或者是HttpServlet的doGet、doPost等通过service方法分发调用的方法),servlet容器将保证对该方法的排队访问,并且不能为其创建实例池。我们强烈建议不要同步service方法和HttpServlet的doGet、doPost这些服务方法。
3.3.3.2. 处理请求过程中的异常
在处理请求过程中,servlet可以抛出ServletException或者UnavailableException异常。ServletException表明处理情况过程中发生了某种错误,容器需要采取适当的方式来清理请求。UnavailableException异常则表示servlet临时性或者永久性不能响应请求。
如果servlet抛出了声明为永久性失效的UnavailableException,servlet容器必须移除这个servlet服务,调用其destroy方法,并释放servlet实例。
如果是临时性失效,在指定的最短失效期内,容器不能把请求路由到这个servlet。容器必须向被拒绝的请求返回SERVICE_UNAVAILABLE(503)响应,并在用Retry-After后信息标明何时再次生效。容器也可以选择不区分临时性失效和永久性失效,将所有UnavailableException当作永久性失效,然后移除servlet服务。
3.3.3.3. 线程安全
请求和响应对象是不保证线程安全的。这意味着它们只应当在请求处理线程范围内被使用。请求对象和响应对象的引用不应当被传递给在其他线程中失效的对象,否则会产生无法预料的结果。
3.3.4. 服务终止
Servlet容器不需要在任何时刻都保持servlet处于被加载的状态。Servlet实例的活动时间可以只有几毫秒,可以和引擎的生命周期一样长(几天,几个月,或者几年),也可以介于两者之间。
当servlet容器检测到应当将一个servlet服务移除的时候(例如容器需要保留内存,或者容器被停止),必须允许servlet释放资源,并保存持久化状态。容器通过调用Servlet接口的destroy方法实现这一点。
在servlet容器调用destroy方法之前,必须先确保所有在servlet方法中运行的线程或者停止,或者超出服务器定义的运行限制时间。
Servlet实例的destroy方法一旦被调用,容器就不能再将请求路由到这个servlet实例。如果容器需要重新提供这个服务,必须使用新的servlet实例。
Destroy方法完成执行后,容器必须释放servlet实例,以便进行垃圾回收。