一、servlet3.0异步处理
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求。即每一次Http请求都由某一个线程从头到尾负责处理。
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。
在以前的servlet中,如果作为控制器的servlet调用了一个较为耗时的业务方法,则servlet必须等到业务执行完后才会生成响应,这使得这次调用成了阻塞式调用,效率比较差。
为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。
Servlet3.0支持异步处理支持,Servlet接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时Servlet还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest和ServletResponse对象的引用),或者将请求继续转发给其他Servlet。
二、同步Servlet
@WebServlet(value = "/sync")
public class HelloSyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(Thread.currentThread()+" start..."+"==>"+System.currentTimeMillis());
try {
sayHello();
} catch (Exception e) {
e.printStackTrace();
}
resp.getWriter().write("hello...");
System.out.println(Thread.currentThread()+" end..."+"==>"+System.currentTimeMillis());
}
public void sayHello() throws Exception {
System.out.println(Thread.currentThread()+" processing...");
Thread.sleep(3000);
}
}
三、配置异步的 servlet 与 filter
(1)通过注解asyncSupported=true实现
(2)通过web.xml配置
<servlet>
<servlet-name>async</servlet-name>
<servlet-class>com.njf.servlet.AsyncServlet</servlet-class>
<async-suppored>true</async-suppored>
</servlet>
<servlet-mapping>
<servlet-name>async</servlet-name>
<url-pattern>/async</url-pattern>
</servlet-mapping>
对于使用传统的部署描述文件web.xml配置Servlet和过滤器的情况,Servlet3.0为<servlet>和<filter>标签增加了<async-supported>子标签,该标签的默认取值为false,要启用异步处理支持,则将其设为true即可。
对于使用Servlet3.0提供的@WebServlet和@WebFilter进行Servlet或过滤器配置的情况,这两个标注都提供了asyncSupported属性,默认该属性的取值为false,要启动异步处理支持,只需将该属性设置为true即可。
四、代码示例
示例1:
@WebServlet(value = "/async", asyncSupported = true) //支持异步
public class HelloAsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、支持异步处理asyncSupported=true
//2、开启异步模式
System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
AsyncContext startAsync = req.startAsync();
//3、业务逻辑进行异步处理;开始异步处理
startAsync.start(new Runnable() {
@Override
public void run() {
try {
System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
sayHello();
startAsync.complete();
//获取到异步上下文
AsyncContext asyncContext = req.getAsyncContext();
//设置异步调用超时时长
asyncContext.setTimeout(3*1000);
//异步Servlet里注册异步监听器
asyncContext.addListener(new MyAsyncListener());
//4、获取响应
ServletResponse response = asyncContext.getResponse();
PrintWriter writer = response.getWriter();
writer.write("hello async...");
writer.flush();
System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
} catch (Exception e) {
}
}
});
System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
}
public void sayHello() throws Exception {
System.out.println(Thread.currentThread()+" processing...");
Thread.sleep(300);
}
}
示例2:
@WebServlet(name = "AsyncServlet", urlPatterns = {"/async01"}, asyncSupported = true)
public class AsyncServlet01 extends HttpServlet {
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决乱码
request.setCharacterEncoding("GBK");
response.setContentType("text/html;charset=GBK");
//通过request获得AsyncContent对象
AsyncContext actx = request.startAsync(); //重点方法**
//异步Servlet里注册异步监听器
actx.addListener(new MyAsyncListener());
//设置异步调用超时时长
actx.setTimeout(30 * 3000);
//启动异步调用的线程
actx.start(new MyThread(actx));//重点方法**
// 直接输出到页面的内容(不等异步完成就直接给页面)
//但这些内容必须放在标签内,否则会在页面输出错误内容,这儿反正我测试是这样,具体不知对不对??
PrintWriter out = response.getWriter();
out.println("<h1>不等异步返回结果就直接返到页面的内容</h1>");
out.flush();
}
}
//异步处理业务的线程类
class MyThread implements Runnable {
private AsyncContext actx;
//构造
public MyThread(AsyncContext actx) {
this.actx = actx;
}
@Override
public void run() {
try {
//等待5秒,模拟处理耗时的业务
Thread.sleep(4 * 1000);
//获得request对象,添加数据给页面
ServletRequest req = actx.getRequest();
req.setAttribute("content", "异步获得的数据");
//将请求dispath到index.jsp页面,该页面的session必须设为false
actx.dispatch("/index.jsp");
} catch (Exception e) {
e.printStackTrace();
}
}
}
示例3:
@WebServlet(urlPatterns = {"/async02"}, asyncSupported = true)
public class AsyncServlet02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("进入Servlet的时间:" + new Date() + ".");
out.flush();
//在子线程中执行业务调用,并由其负责输出响应,主线程退出
AsyncContext ctx = req.startAsync();
new Thread(new Task(ctx)).start();
out.println("结束Servlet的时间:" + new Date() + ".");
out.flush();
}
}
class Task implements Runnable {
private AsyncContext ctx = null;
public Task(AsyncContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
try {
//等待10秒钟,以模拟业务方法的执行
Thread.sleep(10000);
PrintWriter out = ctx.getResponse().getWriter();
out.println("业务处理完毕的时间:" + new Date() + ".");
out.flush();
ctx.complete();
}catch (Exception e){
e.printStackTrace();
}
}
}