springMVC+servlet3.0异步请求之AsyncContext

之前已经讲解过servlet3.0以下怎么实现异步请求,那就是使用jetty的continuation。下面来举例说明使用原生的servlet异步机制来进行异步请求。由于之前已经搭建了一个基于springmvc的框架,下面将会基于springmvc来讲解AsyncContext。首先贴上官网上异步请求的过程描述。

  1. Client sends a request
  2. Servlet container allocates a thread and invokes a servlet in it
  3. The servlet calls request.startAsync(), saves the AsyncContext, and returns
  4. The container thread is exited all the way but the response remains open
  5. Some other thread uses the saved AsyncContext to complete the response
  6. Client receives the response

先贴上代码,然后根据代码来体验异步流程

package com.ds.test.asyncservlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * servlet-3.0以上
 * @Description: 使用原生的servlet3.0的异步特性,加深对异步特性的理解,此处使用了spingmvc的框架,
 *                 但是Controller的异步处理还是使用了servlet的原始写法。
 * @author dingshuo [email protected]
 * @date 2017年3月10日 下午10:52:28
 */
@Controller
@RequestMapping("/asyncServlet")
public class AsyncServlet {

    @RequestMapping(value="/testAsyn",method=RequestMethod.GET)
    public void testAsny(HttpServletRequest request, HttpServletResponse response) throws IOException{
        System.out.println("start");
        PrintWriter out = response.getWriter();
        AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(5000);
        asyncContext.addListener(new MyAsyncListener());
        new Thread(new Work(asyncContext,request)).start();
        out.print("异步执行中");
    }
}

class Work implements Runnable{

    private AsyncContext asyncContext;
    private HttpServletRequest request;

    public Work(AsyncContext asyncContext,HttpServletRequest request) {
        this.asyncContext = asyncContext;
        this.request = request;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(4000);
            //此处通过观察源码,得知需要从request来判断是否超时,否则会一直抛出异常,超时的话,超时参数会置为-1
            if(request.getAsyncContext() != null && asyncContext.getTimeout()>0){
                try {
                    ServletResponse response = asyncContext.getResponse();
                    PrintWriter out = response.getWriter();
                    out.println("后台线程执行完成");
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                asyncContext.complete();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class MyAsyncListener implements AsyncListener{

    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        try {
            AsyncContext asyncContext = asyncEvent.getAsyncContext();
            ServletResponse response = asyncContext.getResponse();
            ServletRequest request = asyncContext.getRequest();
            PrintWriter out= response.getWriter();
            if (request.getAttribute("timeout") != null && 
                    StringUtils.equals("true",request.getAttribute("timeout").toString())) {//超时
                out.println("后台线程执行超时---【回调】");
                System.out.println("异步servlet【onComplete超时】");
            }else {//未超时
                out.println("后台线程执行完成---【回调】");
                System.out.println("异步servlet【onComplete完成】");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("异步servlet错误");
    }

    @Override
    public void onStartAsync(AsyncEvent arg0) throws IOException {
        System.out.println("开始异步servlet");
    }

    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        ServletRequest request = asyncEvent.getAsyncContext().getRequest();
        request.setAttribute("timeout", "true");
        System.out.println("异步servlet【onTimeout超时】");
    }

} 

① springmvc-servlet.xml和web.xml添加异步支持的配置。

springmvc-servlet.xml<mvc:annotation-driven>
    <mvc:async-support default-timeout="5000"/>
</mvc:annotation-driven>

web.xml的所有servlet和filter
<async-supported>true</async-supported>

② 获取异步上下文AsyncContext,并设置好超时时间和异步请求监听器。

超时时间单位是毫秒。
自定义的异步监听器需要实现AsyncListener接口。
onComplete(…):异步任务完成之后通过调用asyncContext.complete()进行回调,或者超时之后自动回调。本例中通过在request中设置了一个属性timeout来判断是超时回调还是执行完成回调。
onError(…):异步请求异常时回调。
onStartAsync(…):获取上下文时调用,这里有一个疑问,此时还未添加监听器,感觉这个方法永远触发不了。
onTimeout(…):异步请求超时时调用。本例中超时之后会再request中设置一个标记,用于onComplete调用时判断。

③ 启动异步任务,当任务执行完成时调用complete()方法,此时会回调onComplete

④ 任务超时会先回调onTimeout,紧接着会回调onComplete

运行结果:
A.当超时时间设置成5000,任务执行时间设置4000。控制台打印结果:
start
异步servlet【onComplete完成】
B.当超时时间设置成5000,任务执行时间设置8000。控制台打印结果:
start
异步servlet【onTimeout超时】
异步servlet【onComplete超时】

由此看出,servlet3.0原生的异步请求超时或者被唤醒之后不会像continuation那样重新模拟一次http请求。onComplete方法不论是在超时或者被唤醒均会被调用。

你可能感兴趣的:(spring,mvc)