在第五章中 已经料及了有四种类型的容器:引擎,主机,上下文和包装。你在前面的章节中也建立了自己的简单地上下文和包装。一个上下文通常有一个或更多的包装,每个包装代表一个Servlet定义。这章将查看CatalinaWrapper接口的标准实现。以被每个HTTP请求调用的方法顺序开始并接着讲解javax.servlet.SingleThreadModel接口。这章主要讲解了StandardWrapper和StandardWrapperValue类。并使用StandardWrapper实例代表Servlet。
Sequence of Methods Invocation 方法调用顺序
对于每个到来的HTTP 请求,连接器调用相关容器的invoke方法。例如,如果连接器关联一个StandardContext的实例,连接器将调用StandardContext实例的invoke方法,稍后会调用它的子容器的所有invoke方法(在这种情况下,子容器将会是StandardWrapper类型)。图1.1演示了当连接器接受一个HTTP请求将发生什么。(回顾第五章中一个容器有一个包含一个或多个Value的管道)
1 连接器创建请求和响应对象
2 连接器调用StandardContext实例的invoke方法
3 StandardContext的invoke方法反过来调用上下文管道的invoke方法。StandardContext的管道的基本值是一个StandardContextValue,所以又调用StandardContextValue的invoke方法。
4 StandardContextValue的invoke方法获取合适的Wrapper来服务请求并调用wrapper的invoke方法。
5 StandardWrapper是一个wrapper的标准的实现类。StandardWrapper实例的invoke方法调用它的管道的invoke方法。
6 StandardWrapper管道的basic Value 是一个StandardWrapperValue。因此它的invoke方法被调用。其invoke方法调用wrapper的allocate方法来获取一个Servlet实例。
7 allocate方法调用load方法加载Servlet,如果Servlet需要被加载的话。
8 load方法调用Servlet的init方法
9 StandardWrapperValue调用Servlet的service方法
注意 StandardContext类的构造器设置了StandardContextValue的实例作为其基本值。
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
namingResources.setContainer(this);
}
注意 StandardWrapper类的构造器设置SimpleWrapperValue的实例作为其基本值
public StandardWrapper() {
super();
pipeline.setBasic(new StandardWrapperValve());
}
在这一章我 我们对于Servlet如何被调用感兴趣。因此我们将查看StandardWrapper和StandardWrapperValue类。在这之前,先看javax.servlet.SingleThreadModel接口。理解这个接口对于理解一个wrapper如何加载一个Servlet至关重要。
SingleThreadModel
一个Servlet可以实现SingleThreadModel接口。实现这个接口的饿实例就被叫做一个STM 即SingleThreadModel Servlet。按照Servlet规范,实现这个接口的目的是保证这个Servlet在某个时间只能处理一个请求。下面是Servlet 2.4规范的一部分:
如果一个Servlet实现了这个接口,你就保证了在Servlet的service方法中不会有两个线程同步执行。Servlet容器能保证这个 通过同步访问这个Servlet的单一实例,或者通过维持池化的Servlet实例并拦截每个新的请求分给一个空闲的Servlet。这个接口不能阻止同步问题导致servlets访问共享资源,比如静态类变量或者超出Servlet范围的类。
许多程序员不仔细读这些,认为实现SimpleThreadModel将保证他们的Servlet线程安全。这不是一个个案。仔细再读一遍上述的引用。
实现SimpleThreadModel将保证在同一时间没有两个线程可以执行Servlet的service方法。然而,为保证执行效率,Servlet容器能创建一个STM Servlet的多个实例。那意味着,STM Servlet的service方法能在不同的实例中并发执行。这里将介绍同步问题,如果Servlet需要访问静态类变量或者超出类范围的其他资源。
多线程安全的错误理解
SimpleThreadModel在Servlet2.4中被废弃了。因为它使Servlet开发人员 对于多线程安全错误理解。然而,Servlet2.3和servlet2.4容器仍必须支持这个接口。
StandardWrapper
StandardWrapper对象的主要任务是加载它负责的Servlet并分配它的一个实例。然而 StandardWrapper不调用Servlet的service方法。这个任务留给StandardWrapperValue对象,由StandardWrapper实例的管道的基本值完成。StandardWrapperValue对象调用StandardWrapper的allocate方法从StandardWrapper获取Servlet实例。一旦获取了Servlet的实例,StandardWrapperValue调用了Servlet的service方法。
当Servlet第一次被请求的时候,StandardWrapper加载Servlet类。StandardWrapper动态加载一个Servlet。因此它需要知道这个Servlet类的完全限定名。你通过传递Servlet类名给StandardWrapper的setServletClass方法来告诉它这个。另外,你调用它的setName方法传递这个Servlet将涉及的名字。
对于当StandardWrapperValue请求它的时候分配的Servlet实例,StandardWrapper必须考虑到这个Servlet是否实现了SimpleThreadModel接口。
对于没有实现SimpleThreadModel接口的Servlet,StandardWrapper将仅加载这个Servlet类一次 并为后面的请求继续返回同样的实例。StandardWrapper实例不需要Servlet的多实例,因为它假定对于多线程调用Servlet的service方法是安全的。如果有必要,这是Servlet程序员的责任来同步访问一个公共资源。
对于一个STM Servlet,事情就不同了。StandardWrapper必须保证不会有两个线程将同时访问STM Servlet的service方法。如果一个StandardWrapper维护这个STM Servlet的单一实例,这里就是它将如何调用STM Servlet的service方法:
Servlet instance = <get an instance of the servlet>;
if ((servlet implementing SingleThreadModel>) {
synchronized (instance) {
instance.service(request, response);
}
}
else {
instance.service(request, response);
}
然而考虑到性能,StandardWrapper维护了一组池化的Servlet实例。
一个wrapper也负责准备一个javax.serlvet.ServletConfig实例,这个可以从Servlet内部获取。下面部分讨论了分配和加载这个Servlet。