Servlet3.1规范翻译 - Servlet接口

https://github.com/javahongxi

Servlet接口是Java Servlet API的核心抽象。所有Servlet类必须直接或间接的实现该接口,或者更通常做法是通过继承一个实现了该接口的类从而复用许多共性功能。目前有GenericServlet和HttpServlet这两个类实现了Servlet接口。大多数情况下,开发者只需要继承HttpServlet去实现自己的Servlet即可。

 

2.1 请求处理方法

Servlet基础接口定义了用于客户端请求处理的service方法。当有请求到达时,该方法由servlet容器路由到一个servlet实例。

Web应用程序的并发请求处理通常需要Web开发人员去设计适合多线程执行的Servlet,从而保证service方法能在一个特定时间点处理多线程并发执行。(注:即Servlet默认是线程不安全的,需要开发人员处理多线程问题)

通常Web容器对于并发请求将使用同一个servlet处理,并且在不同的线程中并发执行service方法。

 

2.1.1 基于Http规范的请求处理方法

HttpServlet抽象子类在Servlet接口基础之上添加了些协议相关的方法,并且这些方法能根据HTTP请求类型自动的由HttpServlet中实现的service方法转发到相应的协议相关的处理方法上。这些方法是:

■ doGet处理HTTP GET请求

■ doPost处理HTTP POST请求

■ doPut处理HTTP PUT请求

■ doDelete处理HTTP DELETE请求

■ doHead处理HTTP HEAD请求

■ doOptions处理HTTP OPTIONS请求

■ doTrace处理HTTP TRACE请求

一般情况下,当开发基于HTTP协议的Servlet时,Servlet开发人员将仅去实现doGet 和 doPost请求处理方法即可。如果开发人员想使用其他处理方法,其使用方式跟之前的是类似的,即HTTP编程都是类似。

 

2.1.2 附加的方法

doPut和doDelete方法允许Servlet开发人员让支持HTTP/1.1的客户端使用这些功能。HttpServlet中的doHead方法可以认为是doGet方法的一个特殊形式,它仅返回由doGet方法产生的header信息。doOptions方法返回当前servlet支持的HTTP方法(译者注:通过Allow响应头返回支持的HTTP操作,如GET、POST)。doTrace方法返回的响应包含TRACE请求的所有头信息。

 

2.1.3 有条件GET支持

HttpServlet定义了用于支持有条件GET操作的getLastModified方法。所谓的有条件GET操作是指客户端通过GET请求获取资源时,当资源自第一次获取那个时间点发生更改后才再次发生数据,否则将使用客户端缓存的数据。在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。

 

2.2 实例数量

通过注解描述的(第8章 注解和可插拔性)或者在Web应用程序的部署描述符(第14章 部署描述符)中描述的servlet声明,控制着servlet容器如何提供servlet实例。

对于未托管在分布式环境中(默认)的servlet而言,servlet容器对于每一个Servlet声明必须且只能产生一个实例。不过,如果Servlet实现了SingleThreadModel接口,servlet容器可以选择实例化多个实例以便处理高负荷请求或者串行化请求到一个特定实例。

如果servlet以分布式方式进行部署,容器可以为每个虚拟机(JVM)的每个Servlet声明产生一个实例。但是,如果在分布式环境中servlet实现了SingleThreadModel接口,此时容器可以为每个容器的JVM实例化多个Servlet实例。

2.2.1 关于 Single Thread Model

SingleThreadModel 接口的作用是保证一个特定servlet实例的service方法在一个时刻仅能被一个线程执行,一定要注意,此保证仅适用于每一个servlet实例,因此容器可以选择池化这些对象。有些对象可以在同一时刻被多个servlet实例访问,如HttpSession实例,可以在一个特定的时间对多个Servlet可用,包括那些实现了SingleThreadModel接口的Servlet。

2.3 Servlet生命周期

Servlet是按照一个严格定义的生命周期被管理,该生命周期规定了Servlet如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过javax.servlet.Servlet 接口中的init、service和destroy这些API来表示,所有Servlet必须直接或间接的实现GenericServlet或HttpServlet抽象类。

2.3.1 加载和实例化

Servlet容器负责加载和实例化Servlet。加载和实例化可以发生在容器启动时,或者延迟初始化直到容器决定有请求需要处理时。当Servlet引擎启动后,servlet容器必须定位所需要的Servlet类。Servlet容器使用普通的Java类加载设施加载Servlet类。可以从本地文件系统或远程文件系统或者其他网络服务加载。加载完Servlet类后,容器就可以实例化它并使用了。

2.3.2 初始化

一旦一个Servlet对象实例化完毕,容器接下来必须在处理客户端请求之前初始化该Servlet实例。初始化的目的是以便Servlet能读取持久化配置数据,初始化一些代价高的资源(比如JDBC API 连接),或者执行一些一次性的动作。容器通过调用Servlet实例的init方法完成初始化,init方法定义在Servlet接口中,并且提供一个唯一的ServletConfig接口实现的对象作为参数,该对象每个Servlet实例一个。

配置对象允许Servlet访问由Web应用配置信息提供的键-值对的初始化参数。该配置对象也提供给Servlet去访问一个ServletContext对象,ServletContext描述了Servlet的运行时环境。请参考第4章,“Servlet Context”获取ServletContext接口的更多信息。

 

2.3.2.1 初始化时的错误条件

在初始化阶段,servlet实现可能抛出UnavailableException或ServletException异常。在这种情况下,Servlet不能放置到活动服务中,而且Servlet容器必须释放它。如果初始化没有成功,destroy方法不应该被调用。

在实例初始化失败后容器可能再实例化和初始化一个新的实例。此规则的例外是,当抛出的UnavailableException表示一个不可用的最小时间,容器在创建和初始化一个新的servlet实例之前必须等待一段时间。

2.3.2.2使用工具时的注意事项

当一个工具加载并内省某个Web应用程序时触发的静态初始化,这种用法与调用init初始化方法是有区别的。在Servlet的init方法没被调用,开发人员不应该假定其处于活动的容器环境内。比如,当某个Servlet仅有静态方法被调用时,不应该与数据库或企业级JavaBean(EJB)容器建立连接。

2.3.3 请求处理

Servlet完成初始化后,Servlet容器就可以使用它处理客户端请求了。客户端请求由ServletRequest类型的request对象表示。Servlet封装响应并返回给请求的客户端,该响应由ServletResponse类型的response对象表示。这两个对象(request和response)是由容器通过参数传递到Servlet接口的service方法的。

在HTTP请求的场景下,容器提供的请求和响应对象具体类型分别是HttpServletRequest 和 HttpServletResponse。

需要注意的是,由Servlet容器初始化的某个Servlet 实例在服务期间,可以在其生命周期中不处理任何请求。

2.3.3.1 多线程问题

Servlet容器可以并发的发送多个请求到Servlet的service方法。为了处理这些请求,Servlet开发者必须为service方法的多线程并发处理做好充足的准备。一个替代的方案是开发人员实现SingleThreadModel接口,由容器保证一个service方法在同一个时间点仅被一个请求线程调用,但是此方案是不推荐的。Servlet容器可以通过串行化访问Servlet的请求,或者维护一个Servlet实例池完成该需求。如果Web应用中的Servlet被标注为分布式的,容器应该为每一个分布式应用程序的JVM维护一个Servlet实例池。

对于那些没有实现SingleThreadModel 接口的Servlet,但是它的service方法(或者是那些HttpServlet中通过service方法分派的doGet、doPost等分派方法)是通过synchronized关键词定义的,Servlet容器不能使用实例池方案,并且只能使用序列化请求进行处理。强烈推荐开发人员不要去同步service方法(或者那些由Service分派的方法),因为这将严重影响性能。

 

2.3.3.2 请求处理时的异常

Servlet在处理一个请求时可能抛出ServletException或UnavailableException异常。ServletException表示在处理请求时出现了一些错误,容器应该采取适当的措施清理掉这个请求。

UnavailableException表示servlet目前无法处理请求,或者临时性的或者永久性的。

如果UnavailableException表示的是一个永久性的不可用,Servlet容器必须从服务中移除这个Servlet,调用它的destroy方法,并释放Servlet实例。所有被容器拒绝的请求,都会返回一个SC_NOT_FOUND (404) 响应。

如果UnavailableException表示的是一个临时性的不可用,容器可以选择在临时不可用的这段时间内路由任何请求到Servlet。所以在这段时间内被容器拒绝的请求,都会返回一个SC_SERVICE_UNAVAILABLE (503)响应状态码,且同时会返回一个Retry-After头指示此Servlet什么时候可用。容器可以选择忽略永久性和临时性不可用的区别,并把UnavailableExceptions视为永久性的,从而Servlet抛出UnavailableException后需要把它从服务中移除。

 

2.3.3.3 异步处理

有时候,Filter及/或 Servlet在生成响应之前必须等待一些资源或事件以便完成请求处理。比如,Servlet在进行生成一个响应之前可能等待一个可用的JDBC连接,或者一个远程web服务的响应,或者一个JMS消息,或者一个应用程序事件。在Servlet中等待是一个低效的操作,因为这是阻塞操作,从而白白占用一个线程或其他一些受限资源。许多线程为了等待一个缓慢的资源比如数据库经常发生阻塞,可能引起线程饥饿,且降低整个Web容器的服务质量。当

Servlet 3.0引入了异步处理请求的能力,使线程可以返回到容器,从而执行更多的任务。当开始异步处理请求时,另一个线程或回调可以或者产生响应,或者调用完成(complete)或请求分派(dispatch),这样,它可以在容器上下文使用AsyncContext.dispatch方法运行。一个典型的异步处理事件顺序是:

1. 请求被接收到,通过一系列如用于验证的等标准的filter之后被传递到Servlet。

2. servlet处理请求参数及(或)内容体从而确定请求的类型。

3. 该servlet发出请求去获取一些资源或数据,例如,发送一个远程web服务请求或加入一个等待JDBC连接的队列。

4. servlet不产生响应并返回。

5. 过了一段时间后,所请求的资源变为可用,此时处理线程继续处理事件,要么在同一个线程,要么通过AsyncContext分派到容器中的一个资源上。

Java企业版的功能,如第15.2.2节,在第15-178页的“Web应用环境”和第15.3.1节,在第15-180页的“EJB调用的安全标识传播”,仅提初始化请求的线程执行,或者请求经过AsyncContext.dispatch方法被分派到容器。Java企业版的功能可能支持由AsyncContext.start(Runnable)方法使用其他线程直接操作响应对象。

第八章描述的@WebServlet和@WebFilter注解有一个属性——asyncSupported,boolean类型默认值为false。当asyncSupported设置为true,应用通过执行startAsync(见下文)可以启动一个单独的线程中进行异步处理,并把请求和响应的引用传递给这个线程,然后退出原始线程所在的容器。这意味着响应将遍历(相反的顺序)与进入时相同的过滤器(或过滤器链)。直到AsyncContext调用complete(见下文)时响应才会被提交。如果异步任务在容器启动的分派之前执行,且调用了startAsync并返回给容器,此时应用需负责处理请求和响应对象的并发访问。

从一个Servlet分派时,把asyncSupported=true设置为false是允许的。这种情况下,当servlet的service方法不支持异步退出时,响应将被提交,且容器负责调用AsyncContext的complete,以便所有感兴趣的AsyncListener得到触发知。过滤器作为清理要完成的异步任务持有的资源的一种机制,也应该使用AsyncListener. onComplete触发的结果。

从一个同步Servlet分派到另一个异步Servlet是非法的。不过与该点不同的是当应用调用startAsync时将抛出IllegalStateException。这将允许servlet只能作为同步的或异步的Servlet。

应用在一个与初始请求所用的不同的线程中等待异步任务直到可以直接写响应,这个线程不知道任何过滤器。如果过滤器想处理新线程中的响应,那就必须在处理进入时的初始请求时包装response,并且把包装的response传递给链中的下一个过滤器,并最终交给Servlet。因此,如果响应是包装的(可能被包装多次,每一个过滤器一次),并且应用处理请求并直接写响应,这将只写响应的包装对象,即任何输出的响应都会由响应的包装对象处理。当应用在一个单独的线程中读请求时,写内容到响应的包装对象,这其实是从请求的包装对象读取,并写到响应的包装对象,因此对包装对象操作的所有输入及(或)输出将继续存在。

如果应用选择这样做的话,它将可以使用AsyncContext从一个新线程发起到容器资源的分派请求。这将允许在容器范围内使用像JSP这种内容生成技术。

除了注解属性外,我们还添加了如下方法/类:

■ ServletRequest

■ public AsyncContext startAsync(ServletRequest req, ServletResponse res)。这个方法的作用是将请求转换为异步模式,并使用给定的请求及响应对象和getAsyncTimeout返回的超时时间初始化它的AsyncContext。ServletRequest和ServletResponse参数必须是与传递给servlet的service或filter的doFilter方法相同的对象,或者是ServletRequestWrapper和ServletResponseWrapper子类的包装对象。当应用退出service方法时,调用该方法必须确保response没有被提交。当调用返回的AsyncContext的AsyncContext.complete或AsyncContext超时并且没有监听器处理超时时,它将被提交。异步超时定时器直到请求和它关联的响应从容器返回时才启动。AsyncContext可以被异步线程用来写响应,它也能用来通知没有关闭和提交的响应。

如果请求在不支持异步操作的servlet或filter范围中调用startAsync,或者响应已经被提交或关闭,或者在同一个分派期间重复调用,这些是非法的。从调用startAsync返回的AsyncContext可以接着被用来进行进一步的异步处理。调用返回的AsyncContext 的hasOriginalRequestResponse()方法将返回false,除非传过去的ServletRequest和ServletResponse参数是最原始的那个或不是应用提供的包装器。

在请求设置为异步模式后,在入站调用期间添加的一些请求及(或)响应的包装器可能需要在异步操作期间一直保持,并且它们关联的资源可能也不会释放,出站方向调用的所有过滤器可以以此作为一个标志。

一个在入站调用期间的过滤器应用的ServletRequestWrapper可以被出站调用的过滤器释放,只有当给定的ServletRequest是由AsyncContext初始化的且通过调用AsyncContext.getRequest()返回的,不包括之前说的ServletRequestWrapper。这规则同样适用于ServletResponseWrapper实例。

■ public AsyncContext startAsync() 是一个简便方法,使用原始请求和响应对象用于异步处理。请注意,如果它们在你想调用此方法之前被包装了,这个方法的使用者应该刷出(flush)响应,确保数据写到被包装的响应中没有丢失。

■ public AsyncContext getAsyncContext() – 返回由startAsync 调用创建的或初始化的AsyncContext。如果请求已经被设置为异步模式,调用getAsyncContext 是非法的。

■ public boolean isAsyncSupported() – 如果请求支持异常处理则返回true,否则返回false。一旦请求传给了过滤器或servlet不支持异步处理(通过指定的注解或声明),异步支持将被禁用。

■ public boolean isAsyncStarted() – 如果请求的异步处理已经开始将返回true,否则返回false。如果这个请求自从被设置为异步模式后已经使用任意一个AsyncContext.dispatch方法分派,或者成功调用了AsynContext.complete 方法,这个方法将返回false。

■ public DispatcherType getDispatcherType() – 返回请求的分派器(dispatcher)类型。容器使用请求的分派器类型来选择需要应用到请求的过滤器。只有匹配分派器类型和url模式(url pattern)的过滤器才会被应用。允许一个过滤器配置多个分派器类型,过滤器可以根据请求的不同分派器类型处理请求。请求的初始分派器类型定义为DispatcherType.REQUEST 。使用RequestDispatcher.forward(ServletRequest, ServletResponse) 或 RequestDispatcher.include(ServletRequest, ServletResponse) 分派时,它们的请求的分派器类型分别是DispatcherType.FORWARD 或 DispatcherType.INCLUDE ,当一个异步请求使用任意一个AsyncContext.dispatch方法分派时该请求的分派器类型是DispatcherType.ASYNC。最后,由容器的错误处理机制分派到错误页面的分派器类型是DispatcherType.ERROR 。

■ AsyncContext – 该类表示在ServletRequest启动的异步操作执行上下文,AsyncContext由之前描述的ServletRequest.startAsync创建并初始化。AsyncContext 的方法:

■ public ServletRequest getRequest() – 返回调用startAsync用于初始化AsyncContext 的请求对象。当在异步周期之前调用了complete或任意一个dispatch方法,调用getRequest将抛出IllegalStateException。   

■ public ServletResponse getResponse() –返回调用startAsync用于初始化AsyncContext 的响应对象。当在异步周期之前调用了complete或任意一个dispatch方法,调用getResponse将抛出IllegalStateException。   

■ public void setTimeout(long timeoutMilliseconds) – 设置异步处理的超时时间,以毫秒为单位。该方法调用将覆盖容器设置的超时时间。如果没有调用setTimeout 设置超时时间,将使用容器默认的超时时间。一个小于等于0的数表示异步操作将永不超时。当调用任意一个ServletRequest.startAsync方法时,一旦容器启动的分派返回到容器,超时时间将应用到AsyncContext。当在异步周期开始时容器启动的分派已经返回到容器后,再设置超时时间是非法的,这将抛出一个IllegalStateException异常。

■ public long getTimeout() – 获取AsyncContext关联的超时时间的毫秒值。该方法返回容器默认的超时时间,或最近一次调用setTimeout设置超时时间。

■ public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) – 注册一个用于接收的onTimeout, onError, onComplete 或 onStartAsync通知的监听器。前三个是与最近通过调用任意ServletRequest.startAsync方法启动的异步周期相关联的。onStartAsync 是与通过任意ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当AsyncListener得到通知,传入到该方法的请求响应对象与AsyncEvent.getSuppliedRequest()和AsyncEvent.getSuppliedResponse()是完全相同的。不应该对这些对象进行读取或写入,因为自从注册了AsyncListener后可能发生了额外的包装,不过可以被用来按顺序释放与它们关联的资源。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。

■ public <T extends AsyncListener> createListener(Class<T> clazz) – 实例化指定的AsyncListener类。返回的AsyncListener实例在使用下文描述的addListener方法注册到AsyncContext之前可能需要进一步的自定义。给定的AsyncListener类必须定义一个用于实例化的空参构造器,该方法支持适用于AsyncListener的所有注解。

■ public void addListener(AsyncListener) – 注册给定的监听器用于接收onTimeout, onError, onComplete 或 onStartAsync通知。前三个是与最近通过调用任意ServletRequest.startAsync方法启动的异步周期相关联的。onStartAsync 是与通过任意ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当AsyncListener接收到通知,如果在请求时调用startAsync(req, res) 或startAsync(),从AsyncEvent会得到同样的请求和响应对象。请求和响应对象可以是或者不是被包装的。异步监听器将以它们添加到请求时的顺序得到通知。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。

■ public void dispatch(String path) – 将用于初始化AsyncContext的请求和响应分派到指定的路径的资源。该路径以相对于初始化AsyncContext 的ServletContext进行解析。与请求查询方法相关的所有路径,必须反映出分派的目标,同时原始请求的URI,上下文,路径信息和查询字符串都可以从请求属性中获取,请求属性定义在9-98页的9.7.2章节,“分派的请求参数”。这些属性必须反映最原始的路径元素,即使在多次分派之后。

■ public void dispatch() – 一个简便方法,使用初始化AsyncContext 时的请求和响应进行分派,如下所示。 如果使用startAsync(ServletRequest, ServletResponse)初始化AsyncContext,且传入的请求是HttpServletRequest的一个实例,则使用HttpServletRequest.getRequestURI()返回的URI进行分派。否则分派的是容器最后分派的请求URI。下面的代码示例2-1,代码示例2-2和代码示例2-3演示了不同情况下分派的目标URI是什么。

代码示例2-1:

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. AsyncContext ac = request.startAsync();  
  3. ...  
  4. ac.dispatch(); // 异步分派到 /url/A  

   

代码示例 2-2 :

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. // 转发到 /url/B  
  3. request.getRequestDispatcher(“/url/B”).forward(request, response);  
  4. // 从FORWARD的目标内启动异步操作  
  5. AsyncContext ac = request.startAsync();  
  6. ac.dispatch(); // 异步分派到 /url/A  
  7.    

代码示例2-3:

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. // 转发到 /url/B  
  3. request.getRequestDispatcher(“/url/B”).forward(request, response);  
  4. // 从FORWARD的目标内启动异步操作  
  5. AsyncContext ac = request.startAsync(request, response);  
  6. ac.dispatch(); //异步分派到 /url/B  

  

■ public void dispatch(ServletContext context, String path) -将用于初始化AsyncContext的请求和响应分派到指定ServletContext的指定路径的资源。

■ 之上定义了dispatch 方法的全部3个变体,调用这些方法且将请求和响应对象传入到容器的一个托管线程后将立即返回,在托管线程中异步操作将被执行。请求的分派器类型设置为异步(ASYNC)。不同于RequestDispatcher.forward(ServletRequest, ServletResponse) 分派,响应的缓冲区和头信息将不会重置,即使响应已经被提交分派也是合法的。控制委托给分派目标的请求和响应,除非调用了ServletRequest.startAsync() 或 ServletRequest.startAsync(ServletRequest, ServletResponse),否则响应将在分派目标执行完成时被关闭。在调用了startAsync方法的容器启动的分派没有返回到容器之前任何dispatch方法的调用将没有任何作用。AsyncListener.onComplete(AsyncEvent), AsyncListener.onTimeout(AsyncEvent)和AsyncListener.onError(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。通过调用ServletRequest.startAsync.启动的每个异步周期至多只有一个异步分派操作。相同的异步周期内任何试图执行其他的异步分派操作是非法的并将导致抛出IllegalStateException。如果后来在已分派的请求上调用startAsync,那么所有的dispatch方法调用将和之上具有相同的限制。

■ 任何在执行dispatch方法期间可能抛出的错误或异常必须由容器抓住和处理,如下所示:

i. 调用所有由AsyncContext创建的并注册到ServletRequest的AsyncListener 实例的AsyncListener.onError(AsyncEvent) 方法, 可以通过AsyncEvent.getThrowable()获取到捕获的Throwable。

ii. 如果没有监听器调用AsyncContext.complete 或任何AsyncContext.dispatch 方法,然后执行一个状态码为HttpServletResponse.SC_INTERNAL_SERVER_ERROR的出错分派,并且可以通过RequestDispatcher.ERROR_EXCEPTION请求属性获取Throwable值。

iii. 如果没有找到匹配的错误页面,或错误页面没有调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用AsyncContext.complete。

■ public boolean hasOriginalRequestAndResponse() – 该方法检查AsyncContext 是否以原始的请求和响应对象调用ServletRequest.startAsync()完成初始化的,或者是否通过调用ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化的,且传入的ServletRequest 和ServletResponse 参数都不是应用提供的包装器,这样的话将返回true。如果AsyncContext 使用包装的请求及(或)响应对象调用ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化,那么将返回false。在请求处于异步模式后,该信息可以被出站方向调用的过滤器使用,用于决定是否在入站调用时添加的请求及(或)响应包装器需要在异步操作期间被维持或者被释放。

■ public void start(Runnable r) – 该方法导致容器分派一个线程,该线程可能来自托管的线程池,用于运行指定的Runnable对象。容器可能传播相应的上下文信息到该Runnable 对象。

■ public void complete() – 如果调用了request.startAsync,则必须调用该方法以完成异步处理并提交和关闭响应。如果请求分派到一个不支持异步操作的Servlet,或者由AsyncContext.dispatch调用的目标servlet之后没有调用startAsync,则complete方法会由容器调用。这种情况下,容器负责当servlet的service方法一退出就调用complete()。 如果startAsync 没有被调用则必须抛出IllegalStateException。在调用ServletRequest.startAsync() 或ServletRequest.startAsync(ServletRequest, ServletResponse) 之后且在调用任意dispatch方法之前的任意时刻调用complete()是合法的。在调用了startAsync方法的容器启动的分派没有返回到容器之前该方法的调用将没有任何作用。AsyncListener.onComplete(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。

■ ServletRequestWrapper

■ public boolean isWrapperFor(ServletRequest req)- 检查该包装器是否递归的包装了给定的ServletRequest,如果是则返回true,否则返回false。

■ ServletResponseWrapper

■ public boolean isWrapperFor(ServletResponse res)- 检查该包装器是否递归的包装了给定的ServletResponse,如果是则返回true,否则返回false。

■ AsyncListener

■ public void onComplete(AsyncEvent event) – 用于通知监听器在Servlet上启动的异步操作完成了。

■ public void onError(AsyncEvent event) – 用于通知监听器异步操作未能完成。

■ public void onStartAsync(AsyncEvent event) – 用于通知监听器正在通过调用一个ServletRequest.startAsync方法启动一个新的异步周期。正在被重新启动的异步操作对应的AsyncContext可以通过调用给定的event上调用AsyncEvent.getAsyncContext获取。

■ 在异步操作超时的情况下,容器必须按照如下步骤运行:

■ 当异步操作启动后调用注册到ServletRequest的所有AsyncListener实例的AsyncListener.onTimeout 方法。

■ 如果没有监听器调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,执行一个状态码为HttpServletResponse.SC_INTERNAL_SERVER_ERROR出错分派。

■ 如果没有找到匹配的错误页面,或者错误页面没有调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用AsyncContext.complete()。

■ 默认情况下是不支持JSP中的异步处理,因为它是用于内容生成且异步处理可能在内容生成之前已经完成。这取决于容器如何处理这种情况。一旦完成了所有的异步活动,使用AsyncContext.dispatch分派到的JSP页面可以用来生成内容。

■ 下面所示的图2-1描述了各种异步操作的状态转换。



 

2.3.3.4 线程安全

除了startAsync 和complete 方法,请求和响应对象的实现都不保证线程安全。这意味着它们应该仅在请求处理线程范围内使用或应用确保线程安全的访问请求和响应对象。

如果应用使用容器管理对象创建一个线程,例如请求或响应对象,这些对象必须在其生命周期内被访问,就像3.10和5.6章节定义的那样。请注意,除了startAsync和complete方法,请求和响应对象不是线程安全的。如果这些对象需要多线程访问,需要同步这些访问或通过包装器添加线程安全语义,比如,同步化调用访问请求属性的方法,或者在线程内为响应对象使用一个局部输出流。

 

2.3.3.5 升级处理

在HTTP/1.1,Upgrade通用头(general-header)允许客户端指定其支持和希望使用的其他通信协议。如果服务器找到合适的切换协议,那么新的协议将在之后的通信中使用。Servlet容器提供了HTTP升级机制。不过,Servlet容器本身不知道任何升级协议。协议处理封装在协议处理器。在容器和协议处理器之间通过字节流进行数据读取或写入。

当收到一个升级(upgrade)请求,servlet可以调用HttpServletRequest.upgrade方法启动升级处理。应用准备和发送一个合适的响应到客户端。退出servlet service方法之后,servlet容器完成所有过滤器的处理并标记连接已交给协议处理器处理。然后调用协议处理器的init方法,传入一个WebConnection以允许协议处理器访问数据流。

Servlet过滤器仅处理初始的HTTP请求和响应,然后它们将不会再参与到后续的通信中。换句话说,一旦请求被升级,它们将不会被调用。

协议处理器(ProtocolHandler)可以使用非阻塞IO(non blocking IO)消费和生产消息。

2.3.4 终止服务(End of Service)

Servlet容器没必要保持装载的Servlet持续任何特定的一段时间。一个Servlet实例可能会在servlet容器内保持活跃(active)持续一段时间(以毫秒为单位),Servlet容器的寿命可能是几天,几个月,或几年,或者是任何之间的时间。

当Servlet容器确定servlet应该从服务中移除时,将调用Servlet接口的destroy方法以允许Servlet释放它使用的任何资源和保存任何持久化的状态。例如,当想要节省内存资源或它被关闭时,容器可以做这个。

在servlet容器调用destroy方法之前,它必须让当前正在执行service方法的任何线程完成执行,或者超过了服务器定义的时间限制。

一旦调用了servlet实例的destroy方法,容器无法再路由其他请求到该servlet实例了。如果容器需要再次使用该servlet,它必须用该servlet类的一个新的实例。在destroy方法完成后,servlet容器必须释放servlet实例以便被垃圾回收。

 

Servlet接口是Java Servlet API的核心抽象。所有Servlet类必须直接或间接的实现该接口,或者更通常做法是通过继承一个实现了该接口的类从而复用许多共性功能。目前有GenericServlet和HttpServlet这两个类实现了Servlet接口。大多数情况下,开发者只需要继承HttpServlet去实现自己的Servlet即可。

 

2.1 请求处理方法

Servlet基础接口定义了用于客户端请求处理的service方法。当有请求到达时,该方法由servlet容器路由到一个servlet实例。

Web应用程序的并发请求处理通常需要Web开发人员去设计适合多线程执行的Servlet,从而保证service方法能在一个特定时间点处理多线程并发执行。(注:即Servlet默认是线程不安全的,需要开发人员处理多线程问题)

通常Web容器对于并发请求将使用同一个servlet处理,并且在不同的线程中并发执行service方法。

 

2.1.1 基于Http规范的请求处理方法

HttpServlet抽象子类在Servlet接口基础之上添加了些协议相关的方法,并且这些方法能根据HTTP请求类型自动的由HttpServlet中实现的service方法转发到相应的协议相关的处理方法上。这些方法是:

■ doGet处理HTTP GET请求

■ doPost处理HTTP POST请求

■ doPut处理HTTP PUT请求

■ doDelete处理HTTP DELETE请求

■ doHead处理HTTP HEAD请求

■ doOptions处理HTTP OPTIONS请求

■ doTrace处理HTTP TRACE请求

一般情况下,当开发基于HTTP协议的Servlet时,Servlet开发人员将仅去实现doGet 和 doPost请求处理方法即可。如果开发人员想使用其他处理方法,其使用方式跟之前的是类似的,即HTTP编程都是类似。

 

2.1.2 附加的方法

doPut和doDelete方法允许Servlet开发人员让支持HTTP/1.1的客户端使用这些功能。HttpServlet中的doHead方法可以认为是doGet方法的一个特殊形式,它仅返回由doGet方法产生的header信息。doOptions方法返回当前servlet支持的HTTP方法(译者注:通过Allow响应头返回支持的HTTP操作,如GET、POST)。doTrace方法返回的响应包含TRACE请求的所有头信息。

 

2.1.3 有条件GET支持

HttpServlet定义了用于支持有条件GET操作的getLastModified方法。所谓的有条件GET操作是指客户端通过GET请求获取资源时,当资源自第一次获取那个时间点发生更改后才再次发生数据,否则将使用客户端缓存的数据。在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。

 

2.2 实例数量

通过注解描述的(第8章 注解和可插拔性)或者在Web应用程序的部署描述符(第14章 部署描述符)中描述的servlet声明,控制着servlet容器如何提供servlet实例。

对于未托管在分布式环境中(默认)的servlet而言,servlet容器对于每一个Servlet声明必须且只能产生一个实例。不过,如果Servlet实现了SingleThreadModel接口,servlet容器可以选择实例化多个实例以便处理高负荷请求或者串行化请求到一个特定实例。

如果servlet以分布式方式进行部署,容器可以为每个虚拟机(JVM)的每个Servlet声明产生一个实例。但是,如果在分布式环境中servlet实现了SingleThreadModel接口,此时容器可以为每个容器的JVM实例化多个Servlet实例。

2.2.1 关于 Single Thread Model

SingleThreadModel 接口的作用是保证一个特定servlet实例的service方法在一个时刻仅能被一个线程执行,一定要注意,此保证仅适用于每一个servlet实例,因此容器可以选择池化这些对象。有些对象可以在同一时刻被多个servlet实例访问,如HttpSession实例,可以在一个特定的时间对多个Servlet可用,包括那些实现了SingleThreadModel接口的Servlet。

2.3 Servlet生命周期

Servlet是按照一个严格定义的生命周期被管理,该生命周期规定了Servlet如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过javax.servlet.Servlet 接口中的init、service和destroy这些API来表示,所有Servlet必须直接或间接的实现GenericServlet或HttpServlet抽象类。

2.3.1 加载和实例化

Servlet容器负责加载和实例化Servlet。加载和实例化可以发生在容器启动时,或者延迟初始化直到容器决定有请求需要处理时。当Servlet引擎启动后,servlet容器必须定位所需要的Servlet类。Servlet容器使用普通的Java类加载设施加载Servlet类。可以从本地文件系统或远程文件系统或者其他网络服务加载。加载完Servlet类后,容器就可以实例化它并使用了。

2.3.2 初始化

一旦一个Servlet对象实例化完毕,容器接下来必须在处理客户端请求之前初始化该Servlet实例。初始化的目的是以便Servlet能读取持久化配置数据,初始化一些代价高的资源(比如JDBC API 连接),或者执行一些一次性的动作。容器通过调用Servlet实例的init方法完成初始化,init方法定义在Servlet接口中,并且提供一个唯一的ServletConfig接口实现的对象作为参数,该对象每个Servlet实例一个。

配置对象允许Servlet访问由Web应用配置信息提供的键-值对的初始化参数。该配置对象也提供给Servlet去访问一个ServletContext对象,ServletContext描述了Servlet的运行时环境。请参考第4章,“Servlet Context”获取ServletContext接口的更多信息。

 

2.3.2.1 初始化时的错误条件

在初始化阶段,servlet实现可能抛出UnavailableException或ServletException异常。在这种情况下,Servlet不能放置到活动服务中,而且Servlet容器必须释放它。如果初始化没有成功,destroy方法不应该被调用。

在实例初始化失败后容器可能再实例化和初始化一个新的实例。此规则的例外是,当抛出的UnavailableException表示一个不可用的最小时间,容器在创建和初始化一个新的servlet实例之前必须等待一段时间。

2.3.2.2使用工具时的注意事项

当一个工具加载并内省某个Web应用程序时触发的静态初始化,这种用法与调用init初始化方法是有区别的。在Servlet的init方法没被调用,开发人员不应该假定其处于活动的容器环境内。比如,当某个Servlet仅有静态方法被调用时,不应该与数据库或企业级JavaBean(EJB)容器建立连接。

2.3.3 请求处理

Servlet完成初始化后,Servlet容器就可以使用它处理客户端请求了。客户端请求由ServletRequest类型的request对象表示。Servlet封装响应并返回给请求的客户端,该响应由ServletResponse类型的response对象表示。这两个对象(request和response)是由容器通过参数传递到Servlet接口的service方法的。

在HTTP请求的场景下,容器提供的请求和响应对象具体类型分别是HttpServletRequest 和 HttpServletResponse。

需要注意的是,由Servlet容器初始化的某个Servlet 实例在服务期间,可以在其生命周期中不处理任何请求。

2.3.3.1 多线程问题

Servlet容器可以并发的发送多个请求到Servlet的service方法。为了处理这些请求,Servlet开发者必须为service方法的多线程并发处理做好充足的准备。一个替代的方案是开发人员实现SingleThreadModel接口,由容器保证一个service方法在同一个时间点仅被一个请求线程调用,但是此方案是不推荐的。Servlet容器可以通过串行化访问Servlet的请求,或者维护一个Servlet实例池完成该需求。如果Web应用中的Servlet被标注为分布式的,容器应该为每一个分布式应用程序的JVM维护一个Servlet实例池。

对于那些没有实现SingleThreadModel 接口的Servlet,但是它的service方法(或者是那些HttpServlet中通过service方法分派的doGet、doPost等分派方法)是通过synchronized关键词定义的,Servlet容器不能使用实例池方案,并且只能使用序列化请求进行处理。强烈推荐开发人员不要去同步service方法(或者那些由Service分派的方法),因为这将严重影响性能。

 

2.3.3.2 请求处理时的异常

Servlet在处理一个请求时可能抛出ServletException或UnavailableException异常。ServletException表示在处理请求时出现了一些错误,容器应该采取适当的措施清理掉这个请求。

UnavailableException表示servlet目前无法处理请求,或者临时性的或者永久性的。

如果UnavailableException表示的是一个永久性的不可用,Servlet容器必须从服务中移除这个Servlet,调用它的destroy方法,并释放Servlet实例。所有被容器拒绝的请求,都会返回一个SC_NOT_FOUND (404) 响应。

如果UnavailableException表示的是一个临时性的不可用,容器可以选择在临时不可用的这段时间内路由任何请求到Servlet。所以在这段时间内被容器拒绝的请求,都会返回一个SC_SERVICE_UNAVAILABLE (503)响应状态码,且同时会返回一个Retry-After头指示此Servlet什么时候可用。容器可以选择忽略永久性和临时性不可用的区别,并把UnavailableExceptions视为永久性的,从而Servlet抛出UnavailableException后需要把它从服务中移除。

 

2.3.3.3 异步处理

有时候,Filter及/或 Servlet在生成响应之前必须等待一些资源或事件以便完成请求处理。比如,Servlet在进行生成一个响应之前可能等待一个可用的JDBC连接,或者一个远程web服务的响应,或者一个JMS消息,或者一个应用程序事件。在Servlet中等待是一个低效的操作,因为这是阻塞操作,从而白白占用一个线程或其他一些受限资源。许多线程为了等待一个缓慢的资源比如数据库经常发生阻塞,可能引起线程饥饿,且降低整个Web容器的服务质量。当

Servlet 3.0引入了异步处理请求的能力,使线程可以返回到容器,从而执行更多的任务。当开始异步处理请求时,另一个线程或回调可以或者产生响应,或者调用完成(complete)或请求分派(dispatch),这样,它可以在容器上下文使用AsyncContext.dispatch方法运行。一个典型的异步处理事件顺序是:

1. 请求被接收到,通过一系列如用于验证的等标准的filter之后被传递到Servlet。

2. servlet处理请求参数及(或)内容体从而确定请求的类型。

3. 该servlet发出请求去获取一些资源或数据,例如,发送一个远程web服务请求或加入一个等待JDBC连接的队列。

4. servlet不产生响应并返回。

5. 过了一段时间后,所请求的资源变为可用,此时处理线程继续处理事件,要么在同一个线程,要么通过AsyncContext分派到容器中的一个资源上。

Java企业版的功能,如第15.2.2节,在第15-178页的“Web应用环境”和第15.3.1节,在第15-180页的“EJB调用的安全标识传播”,仅提初始化请求的线程执行,或者请求经过AsyncContext.dispatch方法被分派到容器。Java企业版的功能可能支持由AsyncContext.start(Runnable)方法使用其他线程直接操作响应对象。

第八章描述的@WebServlet和@WebFilter注解有一个属性——asyncSupported,boolean类型默认值为false。当asyncSupported设置为true,应用通过执行startAsync(见下文)可以启动一个单独的线程中进行异步处理,并把请求和响应的引用传递给这个线程,然后退出原始线程所在的容器。这意味着响应将遍历(相反的顺序)与进入时相同的过滤器(或过滤器链)。直到AsyncContext调用complete(见下文)时响应才会被提交。如果异步任务在容器启动的分派之前执行,且调用了startAsync并返回给容器,此时应用需负责处理请求和响应对象的并发访问。

从一个Servlet分派时,把asyncSupported=true设置为false是允许的。这种情况下,当servlet的service方法不支持异步退出时,响应将被提交,且容器负责调用AsyncContext的complete,以便所有感兴趣的AsyncListener得到触发知。过滤器作为清理要完成的异步任务持有的资源的一种机制,也应该使用AsyncListener. onComplete触发的结果。

从一个同步Servlet分派到另一个异步Servlet是非法的。不过与该点不同的是当应用调用startAsync时将抛出IllegalStateException。这将允许servlet只能作为同步的或异步的Servlet。

应用在一个与初始请求所用的不同的线程中等待异步任务直到可以直接写响应,这个线程不知道任何过滤器。如果过滤器想处理新线程中的响应,那就必须在处理进入时的初始请求时包装response,并且把包装的response传递给链中的下一个过滤器,并最终交给Servlet。因此,如果响应是包装的(可能被包装多次,每一个过滤器一次),并且应用处理请求并直接写响应,这将只写响应的包装对象,即任何输出的响应都会由响应的包装对象处理。当应用在一个单独的线程中读请求时,写内容到响应的包装对象,这其实是从请求的包装对象读取,并写到响应的包装对象,因此对包装对象操作的所有输入及(或)输出将继续存在。

如果应用选择这样做的话,它将可以使用AsyncContext从一个新线程发起到容器资源的分派请求。这将允许在容器范围内使用像JSP这种内容生成技术。

除了注解属性外,我们还添加了如下方法/类:

■ ServletRequest

■ public AsyncContext startAsync(ServletRequest req, ServletResponse res)。这个方法的作用是将请求转换为异步模式,并使用给定的请求及响应对象和getAsyncTimeout返回的超时时间初始化它的AsyncContext。ServletRequest和ServletResponse参数必须是与传递给servlet的service或filter的doFilter方法相同的对象,或者是ServletRequestWrapper和ServletResponseWrapper子类的包装对象。当应用退出service方法时,调用该方法必须确保response没有被提交。当调用返回的AsyncContext的AsyncContext.complete或AsyncContext超时并且没有监听器处理超时时,它将被提交。异步超时定时器直到请求和它关联的响应从容器返回时才启动。AsyncContext可以被异步线程用来写响应,它也能用来通知没有关闭和提交的响应。

如果请求在不支持异步操作的servlet或filter范围中调用startAsync,或者响应已经被提交或关闭,或者在同一个分派期间重复调用,这些是非法的。从调用startAsync返回的AsyncContext可以接着被用来进行进一步的异步处理。调用返回的AsyncContext 的hasOriginalRequestResponse()方法将返回false,除非传过去的ServletRequest和ServletResponse参数是最原始的那个或不是应用提供的包装器。

在请求设置为异步模式后,在入站调用期间添加的一些请求及(或)响应的包装器可能需要在异步操作期间一直保持,并且它们关联的资源可能也不会释放,出站方向调用的所有过滤器可以以此作为一个标志。

一个在入站调用期间的过滤器应用的ServletRequestWrapper可以被出站调用的过滤器释放,只有当给定的ServletRequest是由AsyncContext初始化的且通过调用AsyncContext.getRequest()返回的,不包括之前说的ServletRequestWrapper。这规则同样适用于ServletResponseWrapper实例。

■ public AsyncContext startAsync() 是一个简便方法,使用原始请求和响应对象用于异步处理。请注意,如果它们在你想调用此方法之前被包装了,这个方法的使用者应该刷出(flush)响应,确保数据写到被包装的响应中没有丢失。

■ public AsyncContext getAsyncContext() – 返回由startAsync 调用创建的或初始化的AsyncContext。如果请求已经被设置为异步模式,调用getAsyncContext 是非法的。

■ public boolean isAsyncSupported() – 如果请求支持异常处理则返回true,否则返回false。一旦请求传给了过滤器或servlet不支持异步处理(通过指定的注解或声明),异步支持将被禁用。

■ public boolean isAsyncStarted() – 如果请求的异步处理已经开始将返回true,否则返回false。如果这个请求自从被设置为异步模式后已经使用任意一个AsyncContext.dispatch方法分派,或者成功调用了AsynContext.complete 方法,这个方法将返回false。

■ public DispatcherType getDispatcherType() – 返回请求的分派器(dispatcher)类型。容器使用请求的分派器类型来选择需要应用到请求的过滤器。只有匹配分派器类型和url模式(url pattern)的过滤器才会被应用。允许一个过滤器配置多个分派器类型,过滤器可以根据请求的不同分派器类型处理请求。请求的初始分派器类型定义为DispatcherType.REQUEST 。使用RequestDispatcher.forward(ServletRequest, ServletResponse) 或 RequestDispatcher.include(ServletRequest, ServletResponse) 分派时,它们的请求的分派器类型分别是DispatcherType.FORWARD 或 DispatcherType.INCLUDE ,当一个异步请求使用任意一个AsyncContext.dispatch方法分派时该请求的分派器类型是DispatcherType.ASYNC。最后,由容器的错误处理机制分派到错误页面的分派器类型是DispatcherType.ERROR 。

■ AsyncContext – 该类表示在ServletRequest启动的异步操作执行上下文,AsyncContext由之前描述的ServletRequest.startAsync创建并初始化。AsyncContext 的方法:

■ public ServletRequest getRequest() – 返回调用startAsync用于初始化AsyncContext 的请求对象。当在异步周期之前调用了complete或任意一个dispatch方法,调用getRequest将抛出IllegalStateException。   

■ public ServletResponse getResponse() –返回调用startAsync用于初始化AsyncContext 的响应对象。当在异步周期之前调用了complete或任意一个dispatch方法,调用getResponse将抛出IllegalStateException。   

■ public void setTimeout(long timeoutMilliseconds) – 设置异步处理的超时时间,以毫秒为单位。该方法调用将覆盖容器设置的超时时间。如果没有调用setTimeout 设置超时时间,将使用容器默认的超时时间。一个小于等于0的数表示异步操作将永不超时。当调用任意一个ServletRequest.startAsync方法时,一旦容器启动的分派返回到容器,超时时间将应用到AsyncContext。当在异步周期开始时容器启动的分派已经返回到容器后,再设置超时时间是非法的,这将抛出一个IllegalStateException异常。

■ public long getTimeout() – 获取AsyncContext关联的超时时间的毫秒值。该方法返回容器默认的超时时间,或最近一次调用setTimeout设置超时时间。

■ public void addListener(AsyncListener listener, ServletRequest req, ServletResponse res) – 注册一个用于接收的onTimeout, onError, onComplete 或 onStartAsync通知的监听器。前三个是与最近通过调用任意ServletRequest.startAsync方法启动的异步周期相关联的。onStartAsync 是与通过任意ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当AsyncListener得到通知,传入到该方法的请求响应对象与AsyncEvent.getSuppliedRequest()和AsyncEvent.getSuppliedResponse()是完全相同的。不应该对这些对象进行读取或写入,因为自从注册了AsyncListener后可能发生了额外的包装,不过可以被用来按顺序释放与它们关联的资源。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。

■ public <T extends AsyncListener> createListener(Class<T> clazz) – 实例化指定的AsyncListener类。返回的AsyncListener实例在使用下文描述的addListener方法注册到AsyncContext之前可能需要进一步的自定义。给定的AsyncListener类必须定义一个用于实例化的空参构造器,该方法支持适用于AsyncListener的所有注解。

■ public void addListener(AsyncListener) – 注册给定的监听器用于接收onTimeout, onError, onComplete 或 onStartAsync通知。前三个是与最近通过调用任意ServletRequest.startAsync方法启动的异步周期相关联的。onStartAsync 是与通过任意ServletRequest.startAsync 启动的一个新的异步周期相关联的。异步监听器将以它们添加到请求时的顺序得到通知。当AsyncListener接收到通知,如果在请求时调用startAsync(req, res) 或startAsync(),从AsyncEvent会得到同样的请求和响应对象。请求和响应对象可以是或者不是被包装的。异步监听器将以它们添加到请求时的顺序得到通知。容器启动的分派在异步周期启动后返回到容器后,或者在一个新的异步周期启动之前,调用该方法是非法的,将抛出IllegalStateException。

■ public void dispatch(String path) – 将用于初始化AsyncContext的请求和响应分派到指定的路径的资源。该路径以相对于初始化AsyncContext 的ServletContext进行解析。与请求查询方法相关的所有路径,必须反映出分派的目标,同时原始请求的URI,上下文,路径信息和查询字符串都可以从请求属性中获取,请求属性定义在9-98页的9.7.2章节,“分派的请求参数”。这些属性必须反映最原始的路径元素,即使在多次分派之后。

■ public void dispatch() – 一个简便方法,使用初始化AsyncContext 时的请求和响应进行分派,如下所示。 如果使用startAsync(ServletRequest, ServletResponse)初始化AsyncContext,且传入的请求是HttpServletRequest的一个实例,则使用HttpServletRequest.getRequestURI()返回的URI进行分派。否则分派的是容器最后分派的请求URI。下面的代码示例2-1,代码示例2-2和代码示例2-3演示了不同情况下分派的目标URI是什么。

代码示例2-1:

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. AsyncContext ac = request.startAsync();  
  3. ...  
  4. ac.dispatch(); // 异步分派到 /url/A  

   

代码示例 2-2 :

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. // 转发到 /url/B  
  3. request.getRequestDispatcher(“/url/B”).forward(request, response);  
  4. // 从FORWARD的目标内启动异步操作  
  5. AsyncContext ac = request.startAsync();  
  6. ac.dispatch(); // 异步分派到 /url/A  
  7.    

代码示例2-3:

Java代码   收藏代码
  1. // 请求到 /url/A  
  2. // 转发到 /url/B  
  3. request.getRequestDispatcher(“/url/B”).forward(request, response);  
  4. // 从FORWARD的目标内启动异步操作  
  5. AsyncContext ac = request.startAsync(request, response);  
  6. ac.dispatch(); //异步分派到 /url/B  

  

■ public void dispatch(ServletContext context, String path) -将用于初始化AsyncContext的请求和响应分派到指定ServletContext的指定路径的资源。

■ 之上定义了dispatch 方法的全部3个变体,调用这些方法且将请求和响应对象传入到容器的一个托管线程后将立即返回,在托管线程中异步操作将被执行。请求的分派器类型设置为异步(ASYNC)。不同于RequestDispatcher.forward(ServletRequest, ServletResponse) 分派,响应的缓冲区和头信息将不会重置,即使响应已经被提交分派也是合法的。控制委托给分派目标的请求和响应,除非调用了ServletRequest.startAsync() 或 ServletRequest.startAsync(ServletRequest, ServletResponse),否则响应将在分派目标执行完成时被关闭。在调用了startAsync方法的容器启动的分派没有返回到容器之前任何dispatch方法的调用将没有任何作用。AsyncListener.onComplete(AsyncEvent), AsyncListener.onTimeout(AsyncEvent)和AsyncListener.onError(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。通过调用ServletRequest.startAsync.启动的每个异步周期至多只有一个异步分派操作。相同的异步周期内任何试图执行其他的异步分派操作是非法的并将导致抛出IllegalStateException。如果后来在已分派的请求上调用startAsync,那么所有的dispatch方法调用将和之上具有相同的限制。

■ 任何在执行dispatch方法期间可能抛出的错误或异常必须由容器抓住和处理,如下所示:

i. 调用所有由AsyncContext创建的并注册到ServletRequest的AsyncListener 实例的AsyncListener.onError(AsyncEvent) 方法, 可以通过AsyncEvent.getThrowable()获取到捕获的Throwable。

ii. 如果没有监听器调用AsyncContext.complete 或任何AsyncContext.dispatch 方法,然后执行一个状态码为HttpServletResponse.SC_INTERNAL_SERVER_ERROR的出错分派,并且可以通过RequestDispatcher.ERROR_EXCEPTION请求属性获取Throwable值。

iii. 如果没有找到匹配的错误页面,或错误页面没有调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用AsyncContext.complete。

■ public boolean hasOriginalRequestAndResponse() – 该方法检查AsyncContext 是否以原始的请求和响应对象调用ServletRequest.startAsync()完成初始化的,或者是否通过调用ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化的,且传入的ServletRequest 和ServletResponse 参数都不是应用提供的包装器,这样的话将返回true。如果AsyncContext 使用包装的请求及(或)响应对象调用ServletRequest.startAsync(ServletRequest, ServletResponse)完成初始化,那么将返回false。在请求处于异步模式后,该信息可以被出站方向调用的过滤器使用,用于决定是否在入站调用时添加的请求及(或)响应包装器需要在异步操作期间被维持或者被释放。

■ public void start(Runnable r) – 该方法导致容器分派一个线程,该线程可能来自托管的线程池,用于运行指定的Runnable对象。容器可能传播相应的上下文信息到该Runnable 对象。

■ public void complete() – 如果调用了request.startAsync,则必须调用该方法以完成异步处理并提交和关闭响应。如果请求分派到一个不支持异步操作的Servlet,或者由AsyncContext.dispatch调用的目标servlet之后没有调用startAsync,则complete方法会由容器调用。这种情况下,容器负责当servlet的service方法一退出就调用complete()。 如果startAsync 没有被调用则必须抛出IllegalStateException。在调用ServletRequest.startAsync() 或ServletRequest.startAsync(ServletRequest, ServletResponse) 之后且在调用任意dispatch方法之前的任意时刻调用complete()是合法的。在调用了startAsync方法的容器启动的分派没有返回到容器之前该方法的调用将没有任何作用。AsyncListener.onComplete(AsyncEvent)的调用将被延迟到容器启动的分派返回到容器之后。

■ ServletRequestWrapper

■ public boolean isWrapperFor(ServletRequest req)- 检查该包装器是否递归的包装了给定的ServletRequest,如果是则返回true,否则返回false。

■ ServletResponseWrapper

■ public boolean isWrapperFor(ServletResponse res)- 检查该包装器是否递归的包装了给定的ServletResponse,如果是则返回true,否则返回false。

■ AsyncListener

■ public void onComplete(AsyncEvent event) – 用于通知监听器在Servlet上启动的异步操作完成了。

■ public void onError(AsyncEvent event) – 用于通知监听器异步操作未能完成。

■ public void onStartAsync(AsyncEvent event) – 用于通知监听器正在通过调用一个ServletRequest.startAsync方法启动一个新的异步周期。正在被重新启动的异步操作对应的AsyncContext可以通过调用给定的event上调用AsyncEvent.getAsyncContext获取。

■ 在异步操作超时的情况下,容器必须按照如下步骤运行:

■ 当异步操作启动后调用注册到ServletRequest的所有AsyncListener实例的AsyncListener.onTimeout 方法。

■ 如果没有监听器调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,执行一个状态码为HttpServletResponse.SC_INTERNAL_SERVER_ERROR出错分派。

■ 如果没有找到匹配的错误页面,或者错误页面没有调用AsyncContext.complete() 或任何AsyncContext.dispatch 方法,则容器必须调用AsyncContext.complete()。

■ 默认情况下是不支持JSP中的异步处理,因为它是用于内容生成且异步处理可能在内容生成之前已经完成。这取决于容器如何处理这种情况。一旦完成了所有的异步活动,使用AsyncContext.dispatch分派到的JSP页面可以用来生成内容。

■ 下面所示的图2-1描述了各种异步操作的状态转换。



 

2.3.3.4 线程安全

除了startAsync 和complete 方法,请求和响应对象的实现都不保证线程安全。这意味着它们应该仅在请求处理线程范围内使用或应用确保线程安全的访问请求和响应对象。

如果应用使用容器管理对象创建一个线程,例如请求或响应对象,这些对象必须在其生命周期内被访问,就像3.10和5.6章节定义的那样。请注意,除了startAsync和complete方法,请求和响应对象不是线程安全的。如果这些对象需要多线程访问,需要同步这些访问或通过包装器添加线程安全语义,比如,同步化调用访问请求属性的方法,或者在线程内为响应对象使用一个局部输出流。

 

2.3.3.5 升级处理

在HTTP/1.1,Upgrade通用头(general-header)允许客户端指定其支持和希望使用的其他通信协议。如果服务器找到合适的切换协议,那么新的协议将在之后的通信中使用。Servlet容器提供了HTTP升级机制。不过,Servlet容器本身不知道任何升级协议。协议处理封装在协议处理器。在容器和协议处理器之间通过字节流进行数据读取或写入。

当收到一个升级(upgrade)请求,servlet可以调用HttpServletRequest.upgrade方法启动升级处理。应用准备和发送一个合适的响应到客户端。退出servlet service方法之后,servlet容器完成所有过滤器的处理并标记连接已交给协议处理器处理。然后调用协议处理器的init方法,传入一个WebConnection以允许协议处理器访问数据流。

Servlet过滤器仅处理初始的HTTP请求和响应,然后它们将不会再参与到后续的通信中。换句话说,一旦请求被升级,它们将不会被调用。

协议处理器(ProtocolHandler)可以使用非阻塞IO(non blocking IO)消费和生产消息。

2.3.4 终止服务(End of Service)

Servlet容器没必要保持装载的Servlet持续任何特定的一段时间。一个Servlet实例可能会在servlet容器内保持活跃(active)持续一段时间(以毫秒为单位),Servlet容器的寿命可能是几天,几个月,或几年,或者是任何之间的时间。

当Servlet容器确定servlet应该从服务中移除时,将调用Servlet接口的destroy方法以允许Servlet释放它使用的任何资源和保存任何持久化的状态。例如,当想要节省内存资源或它被关闭时,容器可以做这个。

在servlet容器调用destroy方法之前,它必须让当前正在执行service方法的任何线程完成执行,或者超过了服务器定义的时间限制。

一旦调用了servlet实例的destroy方法,容器无法再路由其他请求到该servlet实例了。如果容器需要再次使用该servlet,它必须用该servlet类的一个新的实例。在destroy方法完成后,servlet容器必须释放servlet实例以便被垃圾回收。

猜你喜欢

转载自wely.iteye.com/blog/2397379