2.12.3 Servlet3提供的异步处理
2.12.3 Servlet3提供的异步处理
在以前的Servlet
规范中,如果Servlet
作为控制器调用了一个耗时的业务方法,那么Servlet
必须等到业务方法完全返回之后才会生成响应,这将使得Servlet
对业务方法的调用变成一种阻塞式的调用,因此效率比较低。Servlet3
规范引入了异步处理来解决这个问题,异步处理允许Servlet
重新发起一条新线程去调用耗时的业务方法,这样就可避免等待。
AsyncContext
Servlet3
的异步处理是通过AsyncContext
类来处理的,Servlet
可通过ServletRequest
的如下两个方开启异步调用、创建AsyncContext
对象。
AsyncContext startAsync()
AsyncContext startAsync(ServletRequest, ServletResponse)
重复调用上面的方法将得到同一个AsyncContext
对象。AsyncContext
对象代表异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch
用于请求、启动后台线程、获取request
、response
对象等功能
程序示例
项目结构
G:\Desktop\随书源码\轻量级Java EE企业应用实战(第5版)\codes\02\2.12\servlet3 ├─async.jsp ├─upload.jsp ├─uploadFiles\ │ └─疯狂iOS讲义(上)—立体图.png └─WEB-INF\ ├─build.xml ├─classes\ │ └─lee\ │ ├─AsyncServlet.class │ ├─GetBooksTarget.class │ ├─MyAsyncListener.class │ └─UploadServlet.class ├─lib\ │ ├─crazyit.jar │ ├─jstl.jar │ ├─leegang.jar │ └─standard.jar ├─src\ │ └─lee\ │ ├─AsyncServlet.java │ ├─GetBooksTarget.java │ ├─MyAsyncListener.java │ └─UploadServlet.java └─web.xml
AsyncServlet.java
下面是一个进行异步处理的Servlet
类
1 | package lee; |
上面的Servlet
类中代码:
1 | // 创建AsyncContext,开始异步调用 |
创建了AsyncContext
对象,并通过该对象以异步方式启动了一条后台线程。该线程执行体模拟调用耗时的业务方法.
GetBooksTarget.java
下面是线程执行体的代码
1 | package lee; |
该线程执行体内让线程暂停5秒来模拟调用耗时的业务方法,最后调用AsyncContext
的dispatch
方法把请求dispatch
到指定JSP
页面.
async.jsp
被异步请求dispatch
的目标页面需要指定session="false"
“,表明该页面不会重新创建session
。下面是async.jsp
页面的代码。
1 | <%@ page contentType="text/html; charset=GBK" language="java" |
上面的页面只是一个普通JSP
页面,只是使用了JSTL
标签库来迭代输出books
集合,因此读者需要将JSTL
的两个JAR
包复制到Web
应用的WEB-INF\lib
路径下。
为Servlet开启异步调用
对于希望启用异步调用的Servlet
而言,开发者必须显式指定开启异步调用,为Servlet
开启异步调用有两种方式。
- 为
@WebServlet
指定asyncSupported=true
属性 - 在
web.xml
文件的<servlet>
元素中增加<async-supported>
子元素
web.xml
例如,希望开启上面Servlet
的异步调用可通过如下配置片段:
1 | <servlet> |
对于支持异步调用的Servlet
来说,当Servlet
以异步方式启用新线程之后,该Servlet
的执行不会被阻塞,该Servlet
将可以向客户端浏览器生成响应——当新线程执行完成后,新线程生成的响应再次被送往客户端浏览器。
通过浏览器访问上面的Servlet
将看到如图2.51所示的页面
异步监听器
当Servlet
启用异步调用的线程之后,该线程的执行过程对开发者是透明的。但在有些情况下,开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可借助于Servlet3
提供的异步监听器来实现。
异步监听器需要实现AsyncListener
接口,实现该接口的监听器类需要实现如下4个方法。
onStartAsync(AsyncEvent even)
:当异步调用开始时触发该方法。onComplete(AsyncEvent even)
:当异步调用完成时触发该方法。onError(AsyncEvent even)
:当异步调用出错时触发该方法。onTimeout(AsyncEvent even)
:当异步调用超时时触发该方法。
MyAsyncListener.java
接下来为上面的异步调用定义如下监听器类。
1 | package lee; |
上面实现的异步监听器类只实现了onStartAsync
、onComplete
两个方法,表明该监听器只能监听异步调用开始、异步调用完成两个事件。
给AsyncContext注册异步监听器
提供了异步监听器之后,还需要通过AsyncContext
来注册监听器,调用该对象的addListener()
方法即可注册监听器。例如,在上面的Servlet
中增加如下代码即可注册监听器:
1 | AsyncContext actx=request.startAsync(); |
为异步调用注册了监听器之后,接下来的异步调用过程将会不断地触发该监听器的不同方法。
虽然上面的MyAsyncListener
监听器类可以监听异步调用开始、异步调用完成两个事件,但从实际运行的结果来看,它并不能监听到异步调用开始事件,这可能是因为注册该监听器时异步调用已经开始了的缘故。
需要指出的是,虽然上面介绍的例子都是基于Servlet
的,但由于Filter
与Servlet
具有很大的相似性,因此Servlet3
规范完全支持在Filter
中使用异步调用。在Filter
中进行异步调用与在Servlet
中进行异步调用的效果完全相似,故此处不再赘述。