在以前的Servlet规范中,如果Servlet作为控制器调用一耗时的业务方法,那么Servlet必须等到业务方法完全返回之后才会生成响应,这将使得Servlet对业务方法的调用变成一种阻塞式的调用,因此效率比较低。Servlet3.0规范的异步处理允许Servlet重新发起一条新线程去调用耗时的业务方法,这就可避免等待;
Servlet3.0的异步处理是通过AsyncContext类来处理的,Servlet可通过ServletRequest的如下两个方法开启异步调用、创建AsyncContext对象:AsyncContext startAsync()、AsyncContext startAsync(ServletRequest, ServletResponse);
1、异步处理Servlet
=>AsyncServlet.java
<span style="font-size:14px;"><span style="white-space:pre"> </span>package lee;</span>
<span style="font-size:14px;">
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.util.*;</span>
<span style="font-size:14px;">
@WebServlet(urlPatterns="/async",asyncSupported=true)
public class AsyncServlet extends HttpServlet
{
@Override
public void doGet(HttpServletRequest request
, HttpServletResponse response) throws IOException,ServletException
{
// 响应类型
response.setContentType("text/html;charset=GBK");
// 输出内容
PrintWriter out = response.getWriter();
out.println("<title>异步调用示例</title>");
out.println("进入Servlet的时间:" + new java.util.Date() + ".<br/>");
out.flush();
// 异步调用
AsyncContext actx = request.startAsync();
// 注册监听
actx.addListener(new MyAsyncListener());
// 超时时长
actx.setTimeout(30*1000);
//启动线程
actx.start(new Executor(actx)); // Executor为自定义线程;
out.println("结束Servlet的时间:" + new java.util.Date() + ".<br/>");
out.flush();
}
}</span>
说明:对于希望启用异步调用的Servlet而言,得必须显示指定开启异步调用,有两种方式:a、为@WebServlet指定asyncSupported=true;b、在web.xml文件的<servlet.../>元素中增加<async-supported.../>子元素,如下
<span style="font-size:14px;"><span style="white-space:pre"> </span><servlet>
<servlet-name>async</servlet-name>
<servlet-class>lee.AsyncServlet</servlet-class>
<!-- 开启异步调用支持 -->
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>async</servlet-name>
<url-pattern>/async</url-pattern>
</servlet-mapping></span>
对于支持异步调用的Servlet来说,当Servlet以异步方式启用新线程之后,该Servlet的执行不会被阻塞,该Servlet将可以向客户端浏览器生成响应,当然,当新线程执行完毕后,新线程生成的响应再次被送往客户端浏览器;2、异步线程:耗时业务
=>Executor.java
<span style="white-space:pre"> </span>package lee;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.util.*;
public class Executor implements Runnable
{
private AsyncContext actx = null;
public Executor(AsyncContext actx)
{
this.actx = actx;
}
// 线程执行体
public void run()
{
try
{
// 等待5秒钟,以模拟耗时业务方法的执行
Thread.sleep(5 * 1000);
// 设置request属性
ServletRequest request = actx.getRequest();
List<String> books = new ArrayList<String>();
books.add("疯狂Java讲义");
books.add("经典Java EE企业应用实战");
books.add("疯狂XML讲义");
request.setAttribute("books" , books);
// 跳转到async.jsp页面:被异步请求dispatch的目标页面需要指定session="false",表明该页面不会重新创建session。
actx.dispatch("/async.jsp");
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
3、异步跳转页面:async.jsp
<span style="font-size:14px;"><span style="white-space:pre"> </span><%@ page contentType="text/html; charset=GBK" language="java" session="false"%>
<%@ 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 java.util.Date());
//完成异步调用
request.getAsyncContext().complete();
%></span>
说明:被异步请求dispatch的目标页面需要指定session="false",表明该页面不会重新创建session;此处使用JSTL标签库来迭代输出books集合,故需要将JSTL的两个JAR包(jstl.jar、standard.jar)复制到web应用的WEB-INF/lib路径下;
4、异步监听器
当Servlet启用异步调用的线程之后,该线程执行过程对开发者是通明的,所以如果想要清楚该异步线程的执行细节(针对特定执行结果进行针对性处理)时,可借助Servlet3.0提供的异步监听器来实现;
异步监听器必须实现AsyncListener接口,并实现如下方法:
(1)、onStartAsync(AsyncEvent event):异步调用开始时触发;
(2)、onComplete(AsyncEvent event):异步调用完成时触发;
(3)、onError(AsyncEvent event):异步调用出错时触发;
(4)、onTimeout(AsyncEvent event):异步调用超时时触发;
=>MyAsyncListener.java
<span style="font-size:14px;"><span style="white-space:pre"> </span>package lee;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class MyAsyncListener
implements AsyncListener
{
public void onStartAsync(AsyncEvent event)
throws IOException
{
System.out.println("------异步调用开始------" + new Date());
}
public void onComplete(AsyncEvent event)
throws IOException
{
System.out.println("------异步调用完成------" + new Date());
}
public void onError(AsyncEvent event)
throws IOException
{}
public void onTimeout(AsyncEvent event)
throws IOException
{}
}</span>
说明:从实际结果可知,它不能监听到异步调用开始事件,这可能是因为注册该监听器时异步调用已经开始的缘故;扩展;由于Filter与Servlet具有很大的相似性,因此Servlet3.0规范完全支持在Filter中使用异步调用。
特别说明:如果能帮助到您,请您留下点滴痕迹,让我知道我的存在是有意义的;如果不能帮助到您,请接受我的歉意;