Java Servlet Specification 3.0 之异步处理



1  接受和发送请求,通过一般过滤器的校验转发给servlet

2  servlet处理请求参数或者/和内容来决定请求的种类

3  servlet分发请求返回响应或者数据。例如,发送一个远程web服务请求或者加入到一个队列中等待一个JDBC连接

4  servlet无需返回一个响应

5 过了一段时间,请求的资源可用,线程继续处理那个事件,要不是在同一个线程中或者使用AsyncContext,分发给容器中的一个资源



Java 企业版功能,比如“Web Application Environment ”和“Propogation of Security Identity in EJB Call” 不仅用于线程执行初始化请求,而且通过AsyncContext.dispatch方法将请求分发给容器。Java企业版功能可能用于其他线程在响应对象上通过AsyncContext.start(Runnable)方法直接操作。


在第八章中描述的@WebServlet和@WebFilter注解有一个属性----asyncSupported 是一个布尔值,默认为false。当这个变量设置为true的时候,应用程序能够通过调用startAsync,在一个单独的线程中开启异步处理,给startAsync方法传递这个请求和响应对象的引用,之后在最初的线程中从容器中退出。这就意味着响应就穿过同样的过滤器或者过滤器链,如同当初请求进来的时候一样。这个响应不会提交直到调用了AsyncContext的Complete方法。如果执行异步任务,这个应用负责处理请求和响应对象的并发访问,这要在调用startAsync方法容器初始化分发返回给容器之前完成。


一个servlet的asyncSupported设置为false,之后设置为true的分发是允许的。在这种情况下,当不支持异步的servlet的service方法退出,将提交响应,这个时候容器调用AsyncContext的Complete方法,将唤醒一些感兴趣的AsyncListener实例。AsyncListener.onComplete通知也应该被过滤器作为一种清除资源的机制来使用,这个资源由异步任务来完成。


从一个异步servlet分发到另一个异步servlet将会是违法的。然而,抛出一个IllegalStateException的决定是不同于应用程序调用startAsync。这将允许一个servlet以一个异步的servlet运行或者以一个同步的servlet运行。


应用程序正在等待的异步任务能直接写到响应中,这是在另一个线程中而不是最初的请求所在的线程中。这个线程不知道任何过滤器。如果一个过滤器想在一个新的线程中操作过滤器,就要在处理最初的请求中的“on the way in”时候封装那个响应,并在链中将封装后的响应传递给另一个过滤器,最终传递给servlet。所以如果响应封装了(可能多次,每个过滤器封装一次),并且应用程序处理那个请求并直接写进响应,这是真的写响应封装,任何添加到响应的输出仍将被响应封装处理。当一个应用在一个独立线程中读取请求,并将输出添加进响应时,它真的读取请求封装,并写进响应封装,所以任何要刻意封装的输入和/或者输出操作将继续发生。


轮流地,如果应用程序选择这样做,其可以使用AsyncContext从一个新的线程分发这个请求到容器中的一个资源。这将会使得内容生成技术比如jsp处于容器的范围之内。


对于注解属性,我们有以下方法来进行操作添加:


         ServletRequest

                 public AsyncContext startAsync(ServletRequest req,ServletResponse res)。这个方法设置请求为异步模式,使用所给的请求和响应对象,和由getAsyncTimeout方法返回的超时来初始化AsyncContext。ServletRequest和ServletResponse参数必须是相同的对象来调用servlet的service方法,或者是过滤器的doFilter方法,或者是要封装它们的ServletRequestWrapper或者ServletResponseWrapper类的子类。这个方法的调用确保这个响应不被提交,当应用退出service方法的时候。而当由返回的AsyncContext调用Complete方法的时候或者AsyncContext超时并且没有关联的监听器处理超时的时候,这个响应将被提交。异步超时的跑表将不会开启直到请求和其关联的响应已经从容器中返回。AsyncContext能用于从异步线程中写进响应。也仅仅用于提醒响应没有关闭和提交。

               

                在这些情况下调用startAsync是非法的,比如,如果请求在servlet的范围内或者过滤器不支持异步操作,或者如果响应已经提交和关闭了,或者在相同的分发中startAsync又一次被调用。调用startAsync方法返回的AsyncContext稍后能用于后面的异步处理。调用AsyncContext.hasOriginalRequestResponse()将返回false,除非传递的ServletResponse和ServletRequest是原始的或者没有经过任何的封装。请求被设置为异步模式后,在输出方向的调用的过滤器可能使用这个指明在输入调用期间添加的请求和/或者响应封装可能在异步操作期间需要呆在某个地方,并且他们相关的资源可能不会释放。在过滤器中输入调用适用的ServletRequestWrapper可能会在过滤器的输出调用中释放掉,如果用于实例化AsyncContext和由AsyncContext.getRequest()返回的ServletRequest,不包含ServletRequestWrapper。这也适用于ServletResponseWrapper实例。


              public AsyncContext startAsync()  提供了一个便利的版本,使用原始的请求和响应对象进行异步处理。请注意如果希望调用SHOULD方法之前封装了响应,刷新这个方法来确保写入封装的响应的数据没有丢失。

             

              public AsyncContext  getAsyncContext() 返回一个创建的AsyncContext实例或者调用startAsync()方法初始化后得到的实例。如果请求没有设置为异步模式调用getAsyncContext方法是违法的。

         

               public boolean  isAsyncSupported()------如果请求支持异步处理返回为true,否则返回false。一旦请求传递到过滤器或者servlet ,如果它们不支持异步处理,那么异步支持也是失效的。(要不通过指定的注解或者声明)

               public Boolean isAsyncStarted()---如果请求开启了异步处理返回true,否则返回false。如果这个请求使用AsyncContext.dispacth方法分发,由于被设置为异步模式,调用了AsyncContext.Complete方法,这个方法就返回false。

               public DispatcherType   getDispatcherType()------返回一个请求的分发类型。一个请求的分发类型用容器用来选择适用这个请求的过滤器。只有匹配分发类型和url模式的过滤器才适用。Allowing a filter
that has been configured for multiple dispatcher types to query a request for
it’s dispatcher type allows the filter to process the request differently
depending on it’s dispatcher type.(暂时不知道怎么翻译)。一个请求的初始分发类型被定义为DispatcherType.REQUEST。通过RequestDispatcher.forward(ServletRequest,ServletResponse) 或者RequestDispatcher.include(ServletReuqest,ServletResponse) 的一个请求的分发类型分别是DispatcherType.Forward或者DispatcherType.Include类型,异步请求的分发类型通过AsyncContext.dispatch方法设定为DispatcherType.Async。最后,由容器错误处理机制分发到一个错误页面的分发类型设定为DispatcherType.Error。



AsyncContext --  这个类表示由ServletRequest开启的异步操作的执行上下文。调用ServletRequest.startAsync创建和初始化一个AsyncContext。其有以下方法:


public ServletRequest getRequest()   - 返回调用startAsync方法之一初始化AsyncContext的请求。


public ServletResponse getResponse() - 返回调用startAsync方法之一的响应,用于初始化AsyncContext


public void setTimeout(long timeoutMilliseonds) - 设置异步处理的超时时间。调用这个方法覆盖由容器设置的超时时间。如果超时时间没有由setTimeout方法指定,就使用容器默认的。时间为0或者更少表明异步操作从没有超时。一旦容器启动分发,在调用ServletRequest.startAsync方法之一返回给容器期间,超时适用AsyncContext。如果在异步循环启动了并且容器启动分发已经返回了容器之后,调用这个方法来设置超时是非法的,将抛出一个IllegalStateException。


public long getTimeout() 获取与AsyncContext关联的超时时间,单位毫秒,这个方法返回容器的默认超时时间或者调用setTimeout方法的设置时间。


public void  addListener(asyncListener,req,res) - 注册超时,错误,和complete通知的消息,通过调用ServletRequest.startAsync方法启动最近的异步循环。异步监听器将被顺序通知,按照它们被添加到请求中的顺序。当AsyncListener被通知时,传递到addListener方法的请求和响应对象与访问AsyncEvent.getSuppliedRequest()和AsyncEvent.getSuppliedResonse()方法返回的是相同的。这些对象不能读或者写。因为虽然注册了所给的AsyncListener,更多封装可能发生,也许用于释放与这些对象关联的资源。在容器初始化分发返回给容器,启动了异步循环后,并且在启动一个新的异步循环之前将导致一个IllegalStateException。


public <T extends AsyncListener> createLisntener(Class<T> clazz) - 实例化给定的AsyncListener类。返回的AsyncListener实例可能会在后面定制,在其调用下面的addListener方法使用AsyncContext注册前。给定的AsyncListener类必须定义一个默认构造函数。,用于实例化。这个方法也支持注解。


public void addListener(asyncListener) - 通过调用ServletRequest.startAsync方法,使用最近的异步循环的超时,错误和Complete,注册给定的通知监听器。如果调用请求的startAsync(req,res)或者startAsync()方法,当监听器被通知的时候,那相同的请求和响应对象从AsyncEvent是可以访问的。请求和响应对象可能或者不一定封装。Async 监听器将被顺序通知 ,按照开始加入请求的顺序。在启动异步循环,容器初始化分发返回容器后,并在启动一个新的异步循环之前,调用这个方法是非法的。将抛出IllegalStateException。


public void  dispatch(path) - 分发请求和响应,用于初始化AsyncContext给定路径的资源。给定路径与ServletContext相关,来初始化AsyncContext。所有与请求的query方法相关的路径必须反射到分发目标,所有的原始请求URI,上下文路径,路径信息和查询字符串可能从请求属性中的获取。这些属性必须与原始路径元素一一对应,即使是多种分发。


public void dispatch() - 提供了一种便利来分发请求和响应对象用于初始化下面的AsyncContext。如果通过startAsync(ServletRequest,ServletResponse)方法初始化AsyncContext,并且传递的请求是HttpServletRequest的一个实例,之后这个分发是由HttpServletRequest.getRequestURI()返回的URI。否则,这个分发是请求的URI,当其最后一次被容器分发时。CODE EXAMPLE 2.1,CODE EXAMPLE 2.2,CODE EXAMPLE 2.3的例子显示了分发的目标URI在不同情况下将是什么样的。


CODE EXAMPLE 2-1

REQUEST to /url/A

AsyncContext ac = request.startAsync();
...
ac.dispatch(); does a ASYNC dispatch to /url/A

CODE EXAMPLE 2-2

REQUEST to /url/A
FORWARD to /url/B
getRequestDispatcher(“/url/B”).forward(request, response);
AsyncContext ac = request.startAsync();
ac.dispatch(); does a ASYNC dispatch to /url/A


CODE EXAMPLE 2-3

REQUEST to /url/A
FORWARD to /url/B
getRequestDispatcher(“/url/B”).forward(request, response);
AsyncContext ac = request.startAsync(request, response);
ac.dispatch(); does a ASYNC dispatch to /url/B

public void dispatch(ServletContext context,String path) - 分发请求和响应,用于在给定的ServletContext初始化给定路径的AsyncContext资源

对于上面的dispatch方法的三种变种,传递请求和响应对象给一个线程(线程中分发操作将执行)管理的容器,调用的方法立即返回。这个请求的调度器类型设置为ASYNC。不像RequestDispatcher.forward(ServletRequest,ServletResponse)分发,分发器的缓冲区和数据头将不被重置,分发也是可以的即使是响应已经被提交了。对请求和响应的控制将委托给分发目标,当分发目标已经完成执行,就关闭响应,除非调用了ServletRequest.startAsync()或者ServletRequest.startAsync(ServletRequest,ServletResponse)。如果在容器初始化分发(调用了startAsync方法)已经返回容器之前,调用了部分分发方法,之后调用将无效直到容器初始化分发已经返回容器。AsyncListener.onComplete(AsyncEvent),AsyncListener.onTimeout(AsyncEvent)和AsyncListener.onError(AsyncEvent)也将推迟调用直到容器初始化分发已经返回到容器。这里每次异步循环至少有一次异步分发操作,以调用ServletRequest.startAsync方法开始。任何尝试在相同的异步循环中执行额外的异步分发操作是非法的。将抛出IllegalStataException。如果分发请求的startAsync方法随后被调用,那么部分dispatch方法也将在相同的限制下调用。


在dispatch方法调用期间会发生错误或者异常,这些必须被容器处理。


   1  调用AsyncListener.onComplete(AsyncEvent)方法,对于使用ServletRequest注册的AsyncListener的实例,ServletRequest用于创建AsyncContext并通过AsyncEvent.getThrowable()返回Throwable变量。

   2  如果没有监听器被AsyncComplete或者被AsyncContext.dispatch方法调用,稍后使用一个值为HttpServletResponse.SC_INTERNAL_SERVER_ERROR的状态码,执行一个错误分发。Throwable的值为RequestDispatcher.ERROR_EXCEPTION的请求属性。

   3  如果没有匹配的错误页面,或者错误页面不调用AsyncContext.complete()方法或者部分AsyncContext.dispatch方法,容器就必须调用AsyncContext.complete()方法额。


public boolean hasOriginalRequestAndResponse() - 这个方法调用ServletRequest.startAsync()方法检查是否用初始的请求和响应对象初始化AsyncContext。或者调用ServletRequest.startAsync(ServletRequest,ServletResponse)初始化AsyncContext。无论是ServletRequest还是ServletResponse参数,这些应用程序提供封装器,在这种情况下返回true。如果使用ServletRequest.startAsync(ServletRequest,ServletResponse)封装的请求或者响应初始的AsyncContext,其返回false。这个信息可能在出站目录中被过滤器调用,在请求被设置为异步模式后,来决定在它们输入调用添加的一些请求或者响应封装器需要在异步模式期间保存或者可能释放。


public void start(Runnable r) - 这个方法引起容器去分发一个线程,可能是从一个管理的线程池中,来运行一个指定的Runnable。容器可能传送合适的上下文的信息给Runnable。

public void complete() - 如果调用request.startAsync,之后这个方法必须被调用来完成异步处理和提交并关闭这个响应。complete方法能被容器调用,如果一个请求分发到一个servlet,其不支持异步处理,或者由AsyncContext.dispatch调用的目标servlet不做一个对startAsync的分布式调用。在这种情况下,容器就调用complete()直到servlet的service方法执行完。如果startAsync没有被调用将抛出一个异常。在调用ServletRequest.startAsync()或者ServletRequest.startAsync(ServletRequest,ServletResponse)后,并且调用dispatch方法之前,可以随时调用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) 用于通知由ServletRequest开启的异步操作完成的监听器

     public void onTimeout(AsyncEvent event)  用于通知由ServletRequest开启的异步操作超时的监听器

     public void onError(AsyncEvent event) 用于通知由ServletRequest开启的异步操作错误的监听器

     public void  onStartAsync(AsyncEvent event) 用于通知调用ServletRequest.startAsync方法的初始化异步循环的监听器。与异步操作对应的AsyncContext异步操作再次初始化,可能通过从给定事件上调用AsyncEvent.getAsyncContext中获取。


在异步操作超时的事件中,容器必须按照以下步骤运行:

    1  调用AsyncListener实例的onTimeout方法,这些实例由ServletRequest注册,其中已经初始化了异步操作。

    2  如果没有监听器调用AsyncListener.complete()或者部分AsyncContext.dispatch(),就执行一个错误分发并带着一个状态改码,其值为HttpServletResponse.SC_INTERNAL_SERVER_ERROR。

    3  如果没有匹配的错误页面,或者错误页面不调用AsyncContext.complete()方法或者部分AsyncContext.dispatch方法,容器必须调用AsyncContext.complete()方法。


JSP中的异步处理默认是不支持的,因为其用于构建内容并且在内容构建之前异步处理已经完成。这个取决于容器如何处理这些情况。只要所有的异步活动处理完了使用AsyncContext.dispatch分发的JSP页面能用于内容构建。


图片2-1 显示了一份图 描述了不同异步操作的状态变化。

异步操作的状态变化图





2.3.3.4 Thread Safe

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

如果由一个应用程序创建的线程使用了容器-管理对象,比如请求或者响应对象,那些对象必须在对象生命周期内访问。注意除了startAsync和complete方法外,请求和响应对象不是线程安全的。如果这些对象在多线程中访问,访问必须是同步的或者封装一下使其线程安全,例如,同步方法的调用来访问请求属性,或者在一个线程内部对于响应对象使用本地输出流。

2.3.4 End of Service


servlet容器不需要保证一个servlet在任意时间内加载。一个servlet实例可能在一个servlet容器中保持活跃几秒钟,或者是servlet容器的整个生命周期(可能是许多天,几个月,几年等等),或者之间的大量时间。


当servlet容器决定从业务中移除一个servlet,调用servlet接口的destory方法允许servlet释放其使用的资源并保存为任何持续化状态。例如,容器可能这样做,当容器想保存内存资源或者关闭的时候。


在servlet容器调用destory方法之前,它必须允许正在servlet的service方法中运行的当前线程完成执行,或者超过服务定义的时间限制。


一旦调用了servlet实例的destory方法,容器可能不会根据请求找到对应的servlet实例。如果容器需要再次用到这个servlet实例,它必须用这个servlet的class重新构造一个实例。


destory方法执行完后,servlet容器必须释放这个servlet实例,这样方便垃圾收集。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值