在以前的Servlet规范中,如果Servlet作为控制器调用了一个耗时的业务方法,那么Servlet必须等到业务方法完全返回之后才会生成响应,这将使得Servlet对业务方法的调用变成一种阻塞式的调用,因此效率比较低。Servlet3.0规范引入了异步处理来解决这个问题,异步处理允许Servlet重新发起一条线程去调用耗时的业务方法,这样就可以避免等待。
Servlet3.0的异步处理是通过AsyncContext类来处理的,Servlet可通过ServletRequest的如下两个方法来开启异步调用、创建AsyncContext对象:
-
AsyncContext startAsync()
-
AsyncContext startAsync(ServletRequest, ServletResponse)
重复调用上面的方法将得到同一个AsyncContext对象。AsyncContext对象代表异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch用于请求、启动后台线程、获取request,response对象等功能,下面是一个进行异步处理的Servlet类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@WebServlet
(urlPatterns=
"/async"
, asyncSupported=
true
)
public
class
AsyncServlet
extends
HttpServlet {
public
void
doGet(HttpServletRequest req, HttpServletResponse resp)
throws
IOException, ServletException {
response.setContentType(
"text/html;charset=GBK"
);
PrintWriter out = response.getWriter();
out.println(
"进入Servlet的时间:"
+
new
Date() +
".<br/>"
);
out.flush();
AsyncContext acontext = request.startAsync();
acontext.setTimeout(
20
*
1000
);
acontext.start(
new
Executor(acontext));
out.println(
"结束Servlet的时间:"
+
new
Date() +
".<br/>"
);
out.flush();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
Executor
implements
Runnable {
private
AsyncContext context;
public
Executor(AsyncContext context) {
this
.context = context;}
public
void
run(){
try
{
Thread.sleep(
5000
);
ServletRequest request = context.getRequest();
List<String> books =
new
ArrayList<String>();
books.add(
"book1"
); books.add(
"book2"
); books.add(
"book3"
);
request.setAttribute(
"books"
, books);
context.dispatch(
"/async.jsp"
);
}
catch
(Exception e) {
e.printStachTrace();
}
}
}
|
在此Executor中,让线程睡眠5秒来模拟调用的耗时的业务方法,最后调用AsyncContext的dispatch方法把请求转发到指定的JSP页面。被异步请求的JSP页面需要指定session="false",表明该页面不会重新创建Session,下面是async.jsp的内容:
1
2
3
4
5
6
7
8
9
10
11
|
<%@ page contentType="text/html;chaset=GBK" language="java" session="fasle" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<
ul
>
<
c:forEach
items
=
"${books}"
var
=
"book"
>
<
li
>${book}</
li
>
</
c:forEach
>
</
ul
>
<%
out.println("业务调用结束的时间:" + new Date());
request.getAsyncContext().complete();//完成异步调用
%>
|
上面的页面只是一个JSP页面,只是使用了JSTL标签库来迭代输出books集合,因此需要将JSTL的两个Jar包复制到项目的lib目录中。
对于希望启用异步调用的Servlet而言,开发者必须显示指定开启异步调用,为Servlet开启异步调用有两种方式:1). 在@WebServlet中指定asyncSupported=true 2). 在web.xml的<servlet>元素中增加<async-supported>子元素,以下是一个配置片段:
1
2
3
4
5
6
7
8
9
|
<
servlet
>
<
servlet-name
>async</
servlet-name
>
<
servlet-class
>com.abc.AsyncServlet</
servlet-class
>
<
async-supported
>true</
async-supported
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>async</
servlet-name
>
<
url-pattern
>/async</
url-pattern
>
</
servlet-mapping
>
|
对于支持异步调用的Servlet来说,当Servlet以异步方式启用新线程后,该Servlet的执行不会被阻塞,该Servlet将可以向客户端浏览器生成相应——当新线程执行完成后,新线程生成的相应将再次被送往客户端浏览器。
当Servlet启用异步调用的线程之后,该线程的执行过程对开发者是透明的。但在有些情况下,开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可以借助于Servlet3.0提供的异步监听器来实现。异步监听器需要实现AsyncListener接口,该接口中定义了如下方法:
-
onStartAsync(AsyncEvent event):当异步调用开始时触发该方法
-
onComplete(AsyncEvent event):当异步调用结束时触发该方法
-
onError(AsyncEvent event):当异步调用出错时触发该方法
-
onTimeout(AsyncEvent event):当异步调用超时时触发该方法
下面是一个简单的监听器类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
MyAsyncListener
implements
AsyncListener {
public
void
onComplete(AsyncEvent event) {
System.out.println(
"异步调用完成:"
+
new
Date());
}
public
void
onError(AsyncEvent event) {
System.out.println(
"异步调用出错:"
+
new
Date());
}
public
void
onStartAsync(AsyncEvent event) {
System.out.println(
"异步调用开始:"
+
new
Date());
}
public
void
onTimeout(AsyncEvent event) {
System.out.println(
"异步调用超时:"
+
new
Date());
}
}
|
提供了监听器之后,还需要通过AsynContext来注册此监听器,调用该对象的addListener()方法即可完成监听器的注册:
1
2
|
AsyncContext acontext = request.startAsync();
acontext.addListener(
new
MyAsyncListener());
|