Servlet3-异步请求

更多请移步: 我的博客

第22章 异步请求

Servlet3.0规范新增了对异步请求的支持,Spring MVC也在此基础上对异步请求提供了方便。异步请求是在处理比较耗时的业务时先将request返回,然后另起线程处理耗时的业务,处理完后再返回给用户。

异步请求可以给我们带来很多方便,最直接的用法就是处理耗时的业务,比如,需要查询数据库、需要调用别的服务器来处理等情况下可以先将请求返回给客户端,然后启用新线程处理耗时业务,等处理完成后再将结果返回给用户。稍微扩展一下还可以实现订阅者模式的消息订阅功能,比如,当有异常情况发生时可以主动将相关信息发给运维人员,还有现在很多邮箱系统中收到新邮件的自动提示功能也是这种技术。甚至更进一步的使用方式是在浏览器上做即时通信的程序!

HTTP协议是单向的,只能客户端自己拉不能服务器主动推,Servlet对异步请求的支持并没有修改HTTP协议,而是对Http的巧妙利用。异步请求的核心原理主要分为两大类,一类是轮询,另一类是长连接。轮询就是定时自动发起请求检查有没有需要返回的数据,这种方式对资源的浪费是比较大的;长连接的原理是在客户端发起请求,服务端处理并返回后并不结束连接,这样就可以在后面再次运回给客户端数据。Servlet对异步请求的支持其实采用的是长连接的方式,也就是说,异步请求中在原始的请求返回的时候并没有关闭连接,关闭的只是处理请求的那个线程(一般是回收的线程池里了),只有在异步请求全部处理完之后才会关闭连接。

22.1 Servlet3.O对异步请求的支持

在Servlet3.0规范巾使用异步处理请求非常简单,只需要在请求处理过程中调用request的startAsync方法即可,其返回值是AsyncContext类型。

AsyncContext在异步请求中充当着非常重要的角色,可以称为异步请求上下文也可以称为异步请求容器,无论叫什么其实就是个名字,它的作用是保存与异步请求相关的所有信息,类似于Servlet中的ServletContext。异步请求主要是使用AsyncContext进行操作,它是在请求处理的过程中调用Request的startAsync方法返回的,需要注意的是多次调用startAsync方法返回的是同一个AsyncContext。AsyncContext接口定义如下:

public interface AsyncContext {
     


    static final String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";

    static final String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";

    static final String ASYNC_PATH_INFO = "javax.servlet.async.path_info";

    static final String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";

    static final String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";


    public ServletRequest getRequest();

    public ServletResponse getResponse();

    public boolean hasOriginalRequestAndResponse();

    public void dispatch();

    public void dispatch(String path);

    public void complete();

    public void start(Runnable run);

    public void addListener(AsyncListener listener);

    public void addListener(AsyncListener listener,
                            ServletRequest servletRequest,
                            ServletResponse servletResponse);

    public  T createListener(Class clazz)
        throws ServletException; 

    public void setTimeout(long timeout);

    public long getTimeout();

}

其中,getResponse方法用得非常多,它可以获取到response,然后就可以对response进行各种操作了;dispatch方法用于将请求发送到一个新地址,有三个重载实现方法,其中没有参数dispatch方法的会发送到request原来的地址(如果有forward则使用forward后的最后一个地址).一个path参数的dispatch方法直接将path作为地址,两个参数的dispatch方法可以发送给别的应用指定的地址;complete方法用于通知容器请求已经处理完了;start方法用于启动实际处理线程.不过也可以自己创建线程在其中使用AsyncContext保存的信息(如response)进行处理;addListener用于添加监听器;setTimeout方法用于修改超时时间,因为异步请求一般耗时比较长,而正常的请求设置的有效时长一般比较短,所以在异步请求中很多时候都需要修改超时的时间。

22.1.1 Servlet 3.0处理异步请求实例

使用Servlet 3.0处理异步请求需要三步:①配置Servlet时将async-supported设置为true;②在Servlet处理方法中调用Request的startAsync方法启动异步处理;③使用第2步中返同的
AsyncContext处理异步请求。

要想使用Servlet 3.0异步请求的功能需要在配置Servlet时将async-supported设置为true,比如,配置一个叫WorkServlet的可以处理异步请求的Servlet。


    WorkServlet
    com.excelib.servlet.WorkServlet
    true


    WorkServlet
    /work

然后新建一个叫WorkServlet的Servlet,代码如下:

package com.excelib.servlet;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.*;

public class WorkServlet extends HttpServlet {
     
    private static final long serialVersionUID = 1L;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        // 设置contentType、关闭缓存
        res.setContentType("text/plain;charset=UTF-8");
        res.setHeader("Cache-Control", "private");
        res.setHeader("Pragma", "no-cache");
        // 原始请求可以做一些简单业务的处理
        final PrintWriter writer = res.getWriter();
        writer.println("老板检查当前需要做的工作");
        writer.flush();
        // jobs表示需要做的工作,使用循环模拟初始化
        List jobs = new ArrayList<>();
        for(int i=0;i<10;i++){
            jobs.add("job"+i);
        }
        // 使用request的startAsync方法开启异步处理
        final AsyncContext ac = req.startAsync();
        // 具体处理请求,内部处理启用了新线程,不会阻塞当前线程
        doWork(ac, jobs);
        writer.println("老板布置完工作就走了");
        writer.flush();
    }

    private void doWork(AsyncContext ac, List jobs){
        // 设置超时时间1小时
        ac.setTimeout(1*60*60*1000L);
        // 使用新线程具体处理请求 
        ac.start(new Runnable() {
            @Override
            public void run() {
                try {
                  // 从AsyncContext获取到Response进而获取到Writer
                    PrintWriter w = ac.getResponse().getWriter();
                    for(String job:jobs){
                        w.println("\""+job+"\"请求处理中。。。");
                        Thread.sleep(1 * 1000L); 
                        w.flush();
                    }
                  // 发出请求处理完成通知
                    ac.complete();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

这里的异步处理过程是在doWork方法中,它使用req.startAsync()返回的AsyncContext来处理的请求,处理完成后调用complete方法发出完成通知告诉容器请求已经处理完。doPost中除了startAsync和doWork外都是正常的操作,而且都有注释,就不解析了。当调用诸求时,返回页面结果如图22-1所示。

一个通过异步请求完成工作的示例程序就写完了。

22.1.2异步请求监听器AsyncListener

上面的程序已经可以完成工作了,不过还不够完善。老板这个职业是需要思考宏观问题的,它需要宏观的数据,所以在干完活后最好给领导汇报一下什么时候干完的、干的怎么样、有没有出什么问题等综合性的数据,不过这些事情按照分工并不应该由实际干活的人来做,如果非让它们做就可能会影响效率,而且它们汇报的数据也有可能不真实,所以老板应该找专人来做这件事,这就有了二线人员。在Servlet异步请求中干这个活的二线人员就是AsyncListener监听器,AsyncListener定义如下:

public interface AsyncListener extends EventListener {
     

    public void onComplete(AsyncEvent event) throws IOException;

    public void onTimeout(AsyncEvent event) throws IOException;

    public void onError(AsyncEvent event) throws IOException;

    public void onStartAsync(AsyncEvent event) throws IOException;     

}

onComplete方法在请求处理完成后调用,onTimeout方法在超时后调用,onError方法在出错时调用,onStartAsync方法在Request调用startAsync方法启动异步处理时调用。

这里需要注意的是只有在调用request.startAsync前将监听器添加到AsyncContext,监听器的onStartAsync方法才会起作用,而调用startAsync前AsyncContext还不存在,所以第一次调用startAsync是不会被监听器中的onStartAsync方法捕获的,只有在超时后又重新开始的情况下onStartAsync方法才会起作用。这一般也没有什么太大的问题,就像上面的例子中开始的时候是老板安排的任务,他自己当然知道,所以不汇报也没关系,不过如果到了时间节点任务没完成又重新开始了那还是要汇报的。

我们给前面的WorkServlet添加两个AsyncListener监听器BossListener和LeaderListener.一个用来给老板汇报,另一个用来给项目负责人汇报,它们都是定义在WorkServlet中的私有类,而且代码也都一样,其中BossListener的代码如下:

private class BossListener implements AsyncListener {
     
    final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    public void onComplete(AsyncEvent event) throws IOException {
        System.out.println("在" + formatter.format(new Date()) + "工作处理完成");
    }
    @Override
    public void onError(AsyncEvent event) throws IOException {
        System.out.println("在" + formatter.format(new Date()) + "工作处理出错,详情如下:\t"
            +event.getThrowable().getMessage());
    }
    @Override
    public void onStartAsync(AsyncEvent event) throws IOException {
        System.out.println("在" + formatter.format(new Date()) + "工作处理开始");
    }
    @Override
    public void onTimeout(AsyncEvent event) throws IOException {
        System.out.println("在" + formatter.format(new Date()) + "工作处理超时");
    }
}

然后将监听器注册到WorkServlet中,注册方法是在获取到AsyncContext后将监听器添加进去,相关代码如下:

// 使用request的startAsync方法开启异步处理
final AsyncContext ac = req.startAsync();
// 添加两个监听器
ac.addListener(new BossListener());
ac.addListener(new LeaderListener(), req, res);
// 具体处理请求,内部处理启用了新线程,不会阻塞当前线程
doWork(ac, jobs);
writer.println("老板布置完工作就走了");
writer.flush();

这样就将两个监听器注册完了。这里之所以添加了两个监听器,是要告诉大家一个AsyncContext可以添加多个监听器,而且有两个重载的添加方法。在监听器中可以使用AsyncEvent事件获取Request、Response以及在有异常的时候获取Throwable,代码如下:

event.getSuppliedRequest();
event.getSuppliedReponse();
event.getThrowable();

22.2 Spring MVC中的异步请求

Spring MVC为了方便使用异步请求专门提供了AsyncWebRequest类型的request,并且提供了处理异步请求的管理器WebAsyncManager和工具WebAsyncUtils。

Spring MVC将异步请求细分为了Callable、WebAsyncTask、DeferredResult和ListenableFuture四种类型。前两种是一类,它们的核心是Callable,这一类很容易理解,因为大家对Callable应该都比较熟悉;DeferredResult类可能不是很容易理解,因为它是Spring MVC自己定义的类型,我们平时可能没使用过,而且相关资料也不多,所以刚接触的时候会觉得不知道从哪里人手,不过弄明白后其实是非常简单的;ListenableFuture足Spring MVC4.0新增的,它在Java的Future基础上增加了设置回调方法的功能,主要用于需要在处理器中调用别的资源(如别的url)的情况,Spring MVC专门提供了AsyncRestTemplate方法调用别的资源,并返回ListenableFuture类型。

本章先分析Spring MVC中异步请求使用到的组件,然后分析Spring MVC是怎么使用这些组件处理异步请求的,最后再分别对每一类返回值进行介绍。

22.2.1 Spring MVC中异步请求相关组件

这里主要分析AsyncWebRequest、WebAsyncManager和WebAsyncUtils组件。WebAsyncManager里面还包含了一些别的组件,在分析的过程中也一起分析。
AsyncWebRequest
首先来看AsyncWebRequest,它是专门用来处理异步请求的request,定义如下:

public interface AsyncWebRequest extends NativeWebRequest {
     

    void setTimeout(Long timeout);

    void addTimeoutHandler(Runnable runnable);

    void addCompletionHandler(Runnable runnable);

    void startAsync();

    boolean isAsyncStarted();

    void dispatch();

    boolean isAsyncComplete();

}

其中,addTimeoutHandler方法和addCompletionHandler方法分别用于添加请求超时和请求处理完成的处理器,其作用相当于AsyncListener监听器中的onTimeout和onComplete方法;isAsyncStarted方法用于判断是否启动了异步处理;isAsyncComplete方法用于判断异步处理是否已经处理完了。别的方法都与AsyncContext中的同名方法作用一样,就不一一解释了。它的实现类有两个,一个是NoSupportAsyncWebRequest,另一个是StandardServletAsyncWebRequest,前者不支持异步请求,所以在Spring MVC中实际用作异步请求的request是StandardServletAsync WebRequest.

StandardServletAsyncWebRequest除了实现了AsyncWebRequest接口,还实现了AsyncListener接口,另外还继承了ServletWebRequest,代码如下:

public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener {
     

    private Long timeout;

    private AsyncContext asyncContext;

    private AtomicBoolean asyncCompleted = new AtomicBoolean(false);

    private final List timeoutHandlers = new ArrayList();

    private final List completionHandlers = new ArrayList();


    /**
     * Create a new instance for the given request/response pair.
     * @param request current HTTP request
     * @param response current HTTP response
     */
    public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
        super(request, response);
    }

    /**
     * {@inheritDoc}
     * 

In Servlet 3 async processing, the timeout period begins after the * container processing thread has exited. */ @Override public void setTimeout(Long timeout) { Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress"); this.timeout = timeout; } @Override public void addTimeoutHandler(Runnable timeoutHandler) { this.timeoutHandlers.add(timeoutHandler); } @Override public void addCompletionHandler(Runnable runnable) { this.completionHandlers.add(runnable); } @Override public boolean isAsyncStarted() { return ((this.asyncContext != null) && getRequest().isAsyncStarted()); } /** * Whether async request processing has completed. *

It is important to avoid use of request and response objects after async * processing has completed. Servlet containers often re-use them. */ @Override public boolean isAsyncComplete() { return this.asyncCompleted.get(); } @Override public void startAsync() { Assert.state(getRequest().isAsyncSupported(), "Async support must be enabled on a servlet and for all filters involved " + "in async request processing. This is done in Java code using the Servlet API " + "or by adding \"true\" to servlet and " + "filter declarations in web.xml."); Assert.state(!isAsyncComplete(), "Async processing has already completed"); if (isAsyncStarted()) { return; } this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); this.asyncContext.addListener(this); if (this.timeout != null) { this.asyncContext.setTimeout(this.timeout); } } @Override public void dispatch() { Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); this.asyncContext.dispatch(); } // --------------------------------------------------------------------- // Implementation of AsyncListener methods // --------------------------------------------------------------------- @Override public void onStartAsync(AsyncEvent event) throws IOException { } @Override public void onError(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { for (Runnable handler : this.timeoutHandlers) { handler.run(); } } @Override public void onComplete(AsyncEvent event) throws IOException { for (Runnable handler : this.completionHandlers) { handler.run(); } this.asyncContext = null; this.asyncCompleted.set(true); } }

这里的代码比较长,不过很容易理解,它里面封装了个AsyncContext类型的属性asyncContext,在startAsync方法中会将Request#startAsync返回的AsyncContext设置给它,然后在别的地方主要使用它来完成各种功能。

另外,南于StandardServletAsyncWebRequest实现了AsyncListener接口,所以它自己就是一个监听器,而且在startAsync方法中在创建出AsyncContext后会将自己作为监听器添加进去。监听器实现方法中onStartAsync方法和onError方法是空实现,onTimeout方法和onComplete方法分别调用了封装的两个List类型的属性timeoutHandlers和completionHandlers所保存的Runnable方法,这样在使用时只需要简单地将需要监听超时和处理完成的监听方法添加到这两个属性中就可以了。

WebAsyncManager

WebAsyncManager是Spring MVC处理异步请求过程中最核心的类,它管理着整个异步处理的过程。

WebAsyncManager中最重要的两个方法是startCallableProcessing和startDeferredResultProcessing,这两个方法是启动异步处理的人口方法,它们一共做了三件事:①启动异步处理;②给Request设置相应属性(主要包括timeout、timeoutHandler和completionHandler);③在相应位置调用相应的拦截器。这里的拦截器是Spring MVC自己定义的。

startCallableProcessing方法用于处理Callable和WebAsyncTask类型的异步请求,使用的拦截器类型是CallableProcessingInterceptor,拦截器封装在CallablelnterceptorChain粪型的拦截器链中统一调用。

startDeferredResultProcessing方法用于处理DeferredResult和ListenableFuture类型的异步请求,使用的拦截器是DeferredResultProcessinglnterceptor拦截器,拦截器封装在DeferredResultlnterceptorChain类型的拦截器链中统一调用。

这两个拦截器的定义如下:

public interface CallableProcessingInterceptor {
     

    static final Object RESULT_NONE = new Object();

    static final Object RESPONSE_HANDLED = new Object();

     void  beforeConcurrentHandling(NativeWebRequest request, Callable task) throws Exception;

     void preProcess(NativeWebRequest request, Callable task) throws Exception;

     void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) throws Exception;

     Object handleTimeout(NativeWebRequest request, Callable task) throws Exception;

     void afterCompletion(NativeWebRequest request, Callable task) throws Exception;

}

拦截器的作用就是在不同的时间点通过执行相应的方法来做一些额外的事情,所以要学习一种拦截器主要就是要理解它里边的各个方法执行的时间点。这两拦截器都定义了5个方法,方法名也都一样,而且从名字就很容易理解它们执行的时间点,就不分别解释了。需要注意的是,beforeConcurrentHandling方法是在并发处理前执行的,也就是会在主线程中执行,其他方法都在具体处理请求的子线程中执行。

CallableInterceptorChain和DeferredResultlnterceptorC hain分别用于封装两个Interceptor,它们都是将多个相应的拦截器封装到一个List类型的属性,然后在相应的方法中调用所封装的Interceptor相应方法进行处理。大家是不是很熟悉?它跟前面多次便用的XXXComposite组件类似,也是责任链模式。不过和XXXComposite组件不同的是,这里的方法名与Interceptor中稍有区别,它们的对应关系如下:
1. applyBe foreConcurrentHandling:对应Interceptor中的beforeConcurrentHandling方法。
2. applyPreProcess:对应Interceptor中的preProcess方法。
3. applyPostProcess:对应Interceptor中的postProcess方法。
4. triggerAfterTimeout:对应Interceptor中的afierTimeout方法。
5. triggerAfterCompletion:对应Interceptor中的afterCompletion方法。

理解了这些方法就知道Interceptor和InterceptorChain的作用了,它们都是在WebAsyncManager中相应位置调用的。

在正式分析WebAsyncManager前再看一下WebAsyncTask类,只有理解了这个类才能看明白WebAsyncManager中酌stariCallableProcessing方法。WebAsyncTask的作用主要是封装Callable方法,并且提供了一些异步调用相关的属性,理解了其中包含的属性就明白这个类了,其中属性定义如下:

    private final Callable callable;

    private Long timeout;

    private AsyncTaskExecutor executor;

    private String executorName;

    private BeanFactory beanFactory;

    private Callable timeoutCallback;

    private Runnable completionCallback;

callable用来实际处理请求;timeout用来设置超时时间;executor用来调用callable;executorName用来用容器中注册的名字配置executor;beanFactory用于根据名字获取executor; timeoutCallback相completionCallback分别用于执行超时和请求处理完成的回调。

这里的executor可以直接设置到WebAsyncTask中,也可以使用注册在容器中的名字来设置executorName属性,如果是使用名字来设置的WebAsyncTask的getExecutor方法会从beanFactory中根据名字executorName获取AsyncTaskExecutor,代码如下:

public AsyncTaskExecutor getExecutor() {
    if (this.executor != null) {
        return this.executor;
    }
    else if (this.executorName != null) {
        Assert.state(this.beanFactory != null, "BeanFactory is required to look up an executor bean by name");
        return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);
    }
    else {
        return null;
    }
}

多知道点

如何在Java中使用并发处理


并发处理是通过多线程完成的,在Java中定义一个多线程的任务可以通过实现Runnable或者Callable接口完成,先来看一下Runnable接定义如下:

@FunctionalInterface
public interface Runnable {
     
    /**
     * When an object implementing interface Runnable is used
     * to create a thread, starting the thread causes the object's
     * run method to be called in that separately executing
     * thread.
     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

Runnable里只有一个run方法,我们只需要将需要执行的代码放到里面即可,行需要新建一个线程来调用,示例如下:

Runnable task = new Runnable(){
    @Override
    public void run()
        System.out.println("do task");
    }
    Thread thread = new Thread(task);
    thread.start();
}

这里新建了task的Runnable类型任务,然后使用它创建了Thread并调用start方法执行了任务。需要说明的是,Thread本身也继承了Runnable接口,所以直接使用Thread来创建Runnable类型的任务然后执行,比如,上面的代码可以修改为:

new Thread(){
    @Override
    public void run()
    {
        System.out.println("do task");
    }
}.start();

这样一句代码就可以完成了。

在JavaI.5中新增了Callable接口,定义如下:

public interface Callable<V> {
       
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable里面是call方法,而且可以有返回值还可以处理异常。Callable的执行需要有一个Executor容器来调用,就像Runnable任务需要Thread来调用一样,而且Executor也可以调用Runnable类型的任务。ExecutoriB用后会返回一个Future类型的返回值,我们可以调用Future的get方法来获取Callable中call方法的返回值,不过这个方法是阻塞的,只有call方法执行完后才会返回,示例如下:

ExecutorsService = Executors.newCachedThreadPool();
Callable callableTask = new Callable() {
    public String call() throws Exception{
        Thread.sleep(1000);
        System.out.println("do task");
        return "ok";
    }
};
Future future = executor.submit(callableTask);
System.out.println("after submit task");
String result = future.get();
System.out.println("after future.get()");
System.out.println("result="+result);
executor.shudown();

这里定义了一个Callable类型的callableTask任务,在其call方法中会等待1秒然后输出dotask并返回ok。Executor调用submit方法提交任务后主程序输出aftersubmittask,这个应该在异步任务返回之前输出,因为方法需要等待1秒,输出aftersubmittask后调用future.get(),这时主线程会阻塞,直到call方法返回,然后输出”afterfuture.get()”,最后输出call返回的结果”ok”,程序运行后控制台打印如下:

after submit task
do task
after future.get()
result=ok

下面来看WebAsyncManager,首先介绍它里面的几个重要属性:
1. timeoutCallablelnterceptor:CallableProcessinglnterceptor类型,专门用于Callable和WebAnsyncTask类型超时的拦截器
2. timeoutDeferredResultlnterceptor:DeferredResultProcessinglnterceptor类型,专门用于DeferredResult和ListenableFuture类型超时的拦截器。
3. callablelnterceptors: Map类型,用于所有Callable和WebAsyncTask类型的拦截器。
4. deferredResultlnterceptors:Map类型,用于所有DeferredResult和ListenableFuture类型的拦截器。
5. asyncWebRequest:为了支持异步处理而封装的request。
6. taskExecutor:用于执行Callable和WebAsyncTask类型处理,如果WebAsyncTask中没有定义executor则使用WebAsyncManager中的taskExecutor。

下面分析WebAsyncManager里最核心的两个方法startCallableProcessing和startDeferredResultProcessing,这两个方法的逻辑基本一样,选择其中的startCallableProcessing来分析,这个方法用于启动Callable和WebAsyncTask类型的处理,代码如下:

/**
     * Use the given {@link WebAsyncTask} to configure the task executor as well as
     * the timeout value of the {@code AsyncWebRequest} before delegating to
     * {@link #startCallableProcessing(Callable, Object...)}.
     * @param webAsyncTask a WebAsyncTask containing the target {@code Callable}
     * @param processingContext additional context to save that can be accessed
     * via {@link #getConcurrentResultContext()}
     * @throws Exception if concurrent processing failed to start
     */
    public void startCallableProcessing(final WebAsyncTask webAsyncTask, Object... processingContext) throws Exception {
        Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
        Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");

        Long timeout = webAsyncTask.getTimeout();
        if (timeout != null) {
            this.asyncWebRequest.setTimeout(timeout);
        }

        AsyncTaskExecutor executor = webAsyncTask.getExecutor();
        if (executor != null) {
            this.taskExecutor = executor;
        }

        List interceptors = new ArrayList();
        interceptors.add(webAsyncTask.getInterceptor());
        interceptors.addAll(this.callableInterceptors.values());
        interceptors.add(timeoutCallableInterceptor);

        final Callable callable = webAsyncTask.getCallable();
        final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);

        this.asyncWebRequest.addTimeoutHandler(new Runnable() {
            @Override
            public void run() {
                logger.debug("Processing timeout");
                Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
                if (result != CallableProcessingInterceptor.RESULT_NONE) {
                    setConcurrentResultAndDispatch(result);
                }
            }
        });

        this.asyncWebRequest.addCompletionHandler(new Runnable() {
            @Override
            public void run() {
                interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
            }
        });

        interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
        startAsyncProcessing(processingContext);

        this.taskExecutor.submit(new Runnable() {
            @Override
            public void run() {
                Object result = null;
                try {
                    interceptorChain.applyPreProcess(asyncWebRequest, callable);
                    result = callable.call();
                }
                catch (Throwable ex) {
                    result = ex;
                }
                finally {
                    result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
                }
                setConcurrentResultAndDispatch(result);
            }
        });
    }

通过注释可以看到startCallableProcessing方法主要做了5件事:①将webAsyncTask中相关属性取出并设置到对应的地方;②初始化拦截器链;③给asyncWebRequest设置timeoutHandler和completionHandler;④执行处理器链中相应方法;⑤启动异步处理并使用taskExecutor提交任务。

对其中的启动处理和执行处理详细解释一下,启动处理是调用了startAsyncProcessing方法,其中做了三件事:①调用clearConcurrentResult方法清空之前并发处理的结果;②谰用asyncWebRequest的startAsync方法启动异步处理;③将processingContext设置给concurrentResultContext属性。startAsyncProcessing方法的代码如下:

private void startAsyncProcessing(Object[] processingContext) {
    clearConcurrentResult();
    this.concurrentResultContext = processingContext;
    this.asyncWebRequest.startAsync();

    if (logger.isDebugEnabled()) {
        HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
        String requestUri = urlPathHelper.getRequestUri(request);
        logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
    }
}


/**
 * Clear {@linkplain #getConcurrentResult() concurrentResult} and
 * {@linkplain #getConcurrentResultContext() concurrentResultContext}.
 */
public void clearConcurrentResult() {
    this.concurrentResult = RESULT_NONE;
    this.concurrentResultContext = null;
}

processingContext参数传进来的是处理器中使用的ModelAndViewContainer,concurrentResultContext用来在WebAsyncManager中保存ModelAndViewContainer,在请求处理完成后会设置到RequestMappingHandlerAdapter中,具体过程后面再分析。

下面再来说一下执行处理,执行处理使用的是taskExecutor,不过需要注意的是,这里并没直接使用taskExecutor.submit(callable)来提交,而是提交了新建的Runnable,并将Callable的call方法直接放在run方法里调用。代码如下:

this.taskExecutor.submit(new Runnable() {
    @Override
    public void run() {
        Object result = null;
        try {
            interceptorChain.applyPreProcess(asyncWebRequest, callable);
            result = callable.call();
        }
        catch (Throwable ex) {
            result = ex;
        }
        finally {
            result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
        }
        setConcurrentResultAndDispatch(result);
    }
    });

这么做主要有两个作用:①可以在处理过程中的相应位置调用拦截器链中相应的方法;②在call方法执行完之前不会像Future#get()那样阻塞线程。

不过Runnable是没有返回值的,所以Callable处理的结果需要自己从run方法内部传递出来,WebAsyncManager中专门提供了一个setConcurrentResultAndDispatch方洪来处理返回的结果,这里边会将处理的结果传递出来,代码如下:

private void setConcurrentResultAndDispatch(Object result) {
    synchronized (WebAsyncManager.this) {
        if (hasConcurrentResult()) {
            return;
        }
        this.concurrentResult = result;
    }

    if (this.asyncWebRequest.isAsyncComplete()) {
        logger.error("Could not complete async processing due to timeout or network error");
        return;
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Concurrent result value [" + this.concurrentResult +
                "] - dispatching request to resume processing");
    }

    this.asyncWebRequest.dispatch();
}

concurrentResult是WebAsyncManager中用来保存异步处理结果的属性,hasConcurrentResult方法用来判断concurrentResult是否已经存在返回值。整个方法过程是:如果concurrentResult已经有返回值则直接返回,否则将传人的参数设置到concurrentResult,然后调用asyncWebRequest.isAsyncComplete()检查Request是否已设置为异步处理完成状态(网络中断会造成Request设置为异步处理完成状态),如果是则保存错误日志并返回,否则调用asyncWebRequest.dispatch0发送请求。SpringMVC申异步请求处理完成后会再次发起一个相同的请求,然后在HandlerAdapter中使用一个特殊的HandlerMethod来处理它,具体过程后面再讲解,不过通过Request的dispatch方法发起的请求使用的还是原来的Request,也就是说原来保存在Request中的属性不会丢失。

startDeferredResultProcessing方法和startCallableProcessing方法执行过程类似,只是并没有使用taskExecutor来提交执行,这是因为DeferredResult并不需要执行处理,在后面讲了DeferredResult的用法后大家就明白了。

WebAsyncManager就分析到这里,下面来看WebAsyncUtils。

WebAsyncUtils

WebAsyncUtils里面提供了四个静态方法,其中一个是private权限,只供内部调用的,也就是一共提供了三个供外部使用的静态方法。它们定义如下:
1. public static WebAsyncManager getAsyrtcManager (ServletRequest servletRequest)
2. public static WebAsyncManager getAsyncManager (WebRequest webRequest)
3. `public static AsyncWebRequest createAsyncWebRequest (HttpServletRequest request, HttpServletResponse response)

两重载的getAsyncManager方法通过Request获取WebAsyncManager,它们一个使用ServletRequest类型的Request,一个使用WebRequest类型的Request,获取过程都是先判断Request属性里是否有保存的WebAsyncManager对象,如果有则取出后直接返回,如果没有则新建一个设置到Request的相应属性中并返回,下次再获取时直接从Request属性中取出。

createAsyncWebRequest方法用于创建AsyncWebRequest,它使用ClassUtils.hasMethod判断传人的Request是否包含startAsync方法从而判断是否支持异步处理,如果不支持则新建NoSupportAsyncWebRequest类型的Request并返回,如果支持则调用createStandardServletAsyncWebRequest方法创建StandardServletAsync WebRequest类型的Request并返回。

22.2.2 Spring MVC对异步请求的支持

Spring MVC对异步请求的处理主要在四个地方进行支持,详述如下:
1)FrameworkServlet中给当前请求的WebAsyncManager添加了CallableProcessinglnterceptor类型的拦截器RequestBindinglnterceptor,这是定义在FrameworkServlet内部的一个私有的拦截器,其作用还是跟FrameworkServlet处理正常请求一样,在请求处理前将当前请求的LocaleContext和ServletRequestAttributes设置到了LocaleContextHolder和RequestContextHolder中,并在请求处理完成后恢复,添加过程在processRequest方法中,相关代码如下:

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

private class RequestBindingInterceptor extends CallableProcessingInterceptorAdapter {
     

    @Override
    public  void preProcess(NativeWebRequest webRequest, Callable task) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            HttpServletResponse response = webRequest.getNativeRequest(HttpServletResponse.class);
            initContextHolders(request, buildLocaleContext(request), buildRequestAttributes(request, response, null));
        }
    }
    @Override
    public  void postProcess(NativeWebRequest webRequest, Callable task, Object concurrentResult) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            resetContextHolders(request, null, null);
        }
    }
}

2) RequestMappingHandlerAdapter酌invokeHandleMethod方法提供了对异步请求的核心
支持,其中做了四件跟异步处理相关的事情:

  1. 创建AsyncWebRequest并设置超时时间,具体时间可以通过asyncRequestTimeout属性配置到RequestMappingHandlerAdapter申。
  2. 对当前请求的WebAsyncManager设置了四个属性:taskExecutor,asyncWebRequest,callablelnterceptors和deferredResultlnterceptors,除了asyncWebRequest的另外三个都可以在RequestMappingHandlerAdapter中配置,taskExecutor如果没配置将默认使用SimpleAsyncTaskExecutor。
  3. 如果当前请求是异步请求而且已经处理出了结果,则将异步处理结果与之前保存到WebAsyncManager里的ModeIAnd\fiewContainer取出来,并将WebAsyncManager里的结果清空,然后调用ServletlnvocableHandlerMethod的wrapConcurrentResult方法创建ConcurrentResultHandlerMethod类型(ServletlnvocableHandlerMethod的内部类)的ServletlnvocableHandlerMethod来替换自己,创建出来的ConcurrentResultHandlerMethod并不执行请求,它的主要功能是判断异步处理的结果是不是异常类型,如果是则抛出,如果不是则使用ReturnValueHandler对其进行解析并返回。
  4. 如果requestMappingMethod的invokeAndHandle方法执行完后检查到当前请求已经启动了异步处理,则会直接返回null。

RequestMappingHandlerAdapter中相关代码如下:

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {
    Object result = asyncManager.getConcurrentResult();
    mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    asyncManager.clearConcurrentResult();

    if (logger.isDebugEnabled()) {
        logger.debug("Found concurrent result value [" + result + "]");
    }
    requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
}

requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

if (asyncManager.isConcurrentHandlingStarted()) {
    return null;
}

这里的步骤3是调用了ServletInvocableHandlerMethod的wrapConcurrentResult方法创建了新的ServletlnvocableHandlerMethod来处理异步处理的结果,代码如下:

ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
        return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
    }

ConcurrentResultHandlerMethod是在ServletlnvocableHandlerMethod中定义的继承白ServletInvocableHandlerMethod的内部类,代码如下:

private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");

private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
     

    private final MethodParameter returnType;

    public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
        super(new Callable() {
            @Override
            public Object call() throws Exception {
                if (result instanceof Exception) {
                    throw (Exception) result;
                }
                else if (result instanceof Throwable) {
                    throw new NestedServletException("Async processing failed", (Throwable) result);
                }
                return result;
            }
        }, CALLABLE_METHOD);
        setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
        this.returnType = returnType;
    }

    /**
     * Bridge to actual controller type-level annotations.
     */
    @Override
    public Class getBeanType() {
        return ServletInvocableHandlerMethod.this.getBeanType();
    }

    /**
     * Bridge to actual return value or generic type within the declared
     * async return type, e.g. Foo instead of {@code DeferredResult}.
     */
    @Override
    public MethodParameter getReturnValueType(Object returnValue) {
        return this.returnType;
    }

    /**
     * Bridge to controller method-level annotations.
     */
    @Override
    public  A getMethodAnnotation(Class annotationType) {
        return ServletInvocableHandlerMethod.this.getMethodAnnotation(annotationType);
    }
} 
  

ConcurrentResultHandlerMethod调用父类的构造方法(super)将HandlerMethod中的Handler和Method都替换掉了,Handler用了新建的匿名Callable,Method使用了ServletInvocableHandlerMethod酌静态属性CALLABLE—METHOD,它代码Callable的call方法。新建的Callable的执行逻辑也非常简单,就是判断异步处理的返回值是不是异常类型,如果是则抛出异常,不是则直接返回,然后使用和原来请求一样的返回值处理器处理返回值(因为在构造方法中将原来ServletjnvocableHandlerMethod的返回值处理器设置给了自己)。

3)返回值处理器:一共有四个处理异步请求的返回值处理器,它们分别是AsyncTaskMethodReturnValueHandler、CallableMethodReturnValueHandler、De ferredResultMethodReturn ValueHandler和ListenableFutureReturnValueHandler,每一个对应一种类型的返回值,它们的作用主要是使用WebAsyncManager启动异步处理,后面依次对每一类返回值进行分析。

4)在DispatcherServlet的doDispatch方法中,当HandlerAdapter使用Handler处理完请求耐,会检查是否已经启动了异步处理,如果启动了则不再往下处理,直接返回,相关代码如下:

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
    return;
}

检查方法是调用的WebAsyncManager的isConcurrentHandlingStarted方法,其实内部就是调用的request的isAsyncStarted方法,代码如下:

/**
 * Whether the selected handler for the current request chose to handle the
 * request asynchronously. A return value of "true" indicates concurrent
 * handling is under way and the response will remain open. A return value
 * of "false" means concurrent handling was either not started or possibly
 * that it has completed and the request was dispatched for further
 * processing of the concurrent result.
 */
public boolean isConcurrentHandlingStarted() {
    return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
}

Spring MVC中跟异步请求处理相关的四个位置孰分析完了。主要处理流程是这样的:首先在处理器中返回需要启动异步处理的类型时(四种类型)相应返同值处理器会调用WebAsyncManager的相关方法启动异步处理,然后在DispatcherServlet中将原来请求直接返回,当异步处理完成后会重新发出一个相同的请求,这时在RequestMappingHandlerAdapter中会使用特殊的ServletlnvocableHandlerMethod来处理请求,处理方法是:如果异步处理返回的结果是异常类型则抛出异常,否则直接返回异步处理结果,然后使用返回值处理器处理,接着返回DispatcherServlet中按正常流程往下处理。

异步处理完成后会重新发起一个请求,这时会重新查找HandlerMethod并初始化PathVariable、MatrixVariable等参数,重新初始化Model中的数据并再次执行Handler-Interceptor中相应的方法。这么做主要是可以复用原来的那套组件进行处理而不需要重新定义。不过新请求的HandlerMethod是用的专门的类型,而Model是使用的原来保存在WebAsyncManager的concurrentResultContext届性中的ModelAndViewContainer所保存的Model,所以这里的查找HandlerMethod和初始化Model的过程是没用的,在这里可以进行一些优化,比如,将创建ConcurrentResultHandlerMethod的过程放在HandlerMapping中(这样也更符合组件的功能),然后在调用ModeIFactory的initModel方法前判断是不是异步处理dispatcher过来的请求,如果是
则不再初始化了,或者干脆创建新的HandlerAdapter来处理。

除了上述可以优化的地方,这里还有两个漏洞,第一个是相应的拦截器里的方法会被调用两次,这是不合适的,而且有的时候还会出问题,比如,如果用了拦截器来检查Token.那么第一次检查通过后就会将相应内容删除,第二次再检查的时候就检查失败了,这就有问题了。第二个是通过FlashMap传递Redirect参数的情况,在前面分析FlashMapManager获取FlashMap的时候说过,每次获取后就会将相应的FlashMap删除,但异步请求会获取两次,如果异步处理器是Redirect刭的结果处理器,并且使用FlashMap传递了参数,这种情况下如果在第二次获取FlashMap的时候(异步请求处理完了)正好用户又发了一个相同的请求,而且RedirectView已经将FlashMap设置到了Session,在获取之前可能被前面的请求获取删除,导致自己获取不到,这么说不容易理解,下面将两个请求的处理过程列出来大家就容易理解了:

请求1 请求2
saveOutputFlashMap 设置FM1
retrieveAndUpdate 获取到FM1
saveOutputFlashMap 设置FM2
retrieveAndUpdate 获取到FM2
retrieveAndUpdate 获取到null
retrieveAndUpdate 获取到null

这样请求2设置的FlashMap就会被请求1的第二次retrieveAndUpdate获取到并从Session中删除,请求2就获取不到了,这样两个请求的值就都出了问题了。

这里的第二个漏洞只是从原理上来说存在,一般不会造成什么影响,因为这种情况发生的概率非常小,但第一个漏洞是比较严重的,如果真正使用了类似判断Token等的拦截器需要在具体方法内部自己处理一下。

异步处理流程就说到这里,下面分析每一类返回值的具体处理过程。

22.2.3 WebAsyncTask和Calla ble类型异步请求的处理过程及用法

当处理器方法返回WebAsyncTask或Callable类型时将自动启用异步处理。下面来看一下处理WebAsyncTask类型返回值的处理器AsyncTaskMethodReturnValueH andler.它的handleReturnValue方法如下:

@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    if (returnValue == null) {
        mavContainer.setRequestHandled(true);
        return;
    }

    WebAsyncTask webAsyncTask = (WebAsyncTask) returnValue;
    webAsyncTask.setBeanFactory(this.beanFactory);
    WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(webAsyncTask, mavContainer);
}

如果返回值为null,就会给mavContainer设置为请求已处理,然后返回。如果返回值不为null,调用WebAsyncManager的startCallableProcessing方法处理请求。WebAsyncManager是使用WebAsyncUtils获取的。下面来看一个例子,首先给配置Spring MVC的Servlet添加异步处理支持,也就是添加async-supported属性,代码如下:

<servlet>
    <servlet-name>let'sGoservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>WEB-INF/let'sGo-servlet.xmlparam-value>
    init-param>
    <async-supported>trueasync-supported>
servlet>

接下来写一个AsyncController,代码如下:

package com.excelib.controller;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Controller
public class AsyncController {
     
    @ResponseBody
    @RequestMapping(value = "/webasynctask",produces = "text/plain; charset=UTF-8")
    public WebAsyncTask webAsyncTask(){
        System.out.println("WebAsyncTask处理器主线程进入");
        WebAsyncTask task = new WebAsyncTask(new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5*1000L);
                System.out.println("WebAsyncTask处理执行中。。。");
                return "久等了";
            }
        });
        System.out.println("WebAsyncTask处理器主线程退出");
        return task;
    }
}

这里新建了WebAsyncTask,并使用匿名类建了Callable进行异步处理,实际使用中可以在其中写数据库请求等耗时的业务,这里直接等了5秒来模拟。处理器注释了@ResponseBody,其返回值会直接返回给浏览器。当调用http://localhost:8080/ webasynctask时,会在等待大约5秒后返回给浏览器久等了三个字。

现在再返回去看WebAsyncManager的startCallableProcessing方法就容易理解了,其实就是先添加拦截器,并在相应的地方执行拦截器里的方法,最后使用taskExecutor调用返回WebAsyncTask申的Callable处理。

当然这里只是给WebAsyncTask设置了Callable,除此之外还可以设置executor、timeout、timeoutCallback和completionCallback等属性。

Callable的处理其实是在WebAsyncManager内部封装成WebAsyncTask后再处理的。当处理器中返回Callable类型的返回值时,Spring MVC会使用CallableMethodReturnValueHandler来处理返回值,它的handleReturnValue方法代码如下:

public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
     

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return Callable.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        if (returnValue == null) {
            mavContainer.setRequestHandled(true);
            return;
        }

        Callable callable = (Callable) returnValue;
        WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
    }

}

这里直接调用了WebAsyncManager的startCallableProcessing方法进行处理,不过这是一个重载的第一个参数是Callable类型的startCallableProcessing方法,其代码如下:

public void startCallableProcessing(Callable callable, Object... processingContext) throws Exception {
    Assert.notNull(callable, "Callable must not be null");
    startCallableProcessing(new WebAsyncTask(callable), processingContext);
}

它还是将Callable封装成了WebAsyncTask然后处理的。如果WebAsyncTask中只有Callable而没有别的属性的时候可以直接返回Callable,比如前面的处理器可以修改为:

package com.excelib.controller;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class AsyncController {
     
    @ResponseBody
    @RequestMapping(value = "/callable",produces = "text/plain; charset=UTF-8")
    public Callable callable(){
        System.out.println("Callable处理器主线程进入");
        Callable callable = new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5 * 1000L);
                System.out.println("Callable处理执行中。。。");
                return "久等了";
            }
        };
        System.out.println("Callable处理器主线程退出");
        return callable;
    }
}

它和前面使用WebAsyncTask执行的效果是一样的。

22.2.4 DeferredResult类型异步请求的处理过程及用法

DeferredResult是spring提供的一种用于保存延迟处理结果的类,当一个处理器返回DeferredResult类型的返回值时将启动异步处理。

不过DeferredResult和WebAsyncTask的使用方法完全不同,DeferredResult并不是用于处理请求的,而且也不包含请求的处理过程,它是用来封装处理结果的,有点像Java中的Future,但不完全一样。

使用DeferredResult的难点就在理解其含义,对其含义理解了之后就会觉得非常简单,而且使用起来也很方便。在返回WebAsyncTask时是因为处理的时间过长所以使用了异步处理,但其实还是自己来处理的(因为WebAsyncTask需要提供Callable),而返回DeferredResult表示要将处理交个别人了,什么时候处理完、怎么处理的自己并不需要知道,这就好像在单位经常用到的“妥否,请批示”的请示报告,自己并不知道什么时候能批下来,而且也不需要知道具体批示过程,只需要知道最后的结果就可以了。DeferredResult就是来保存结果的,当处理完之后调用它的setResult方法将结果设置给它就可以了。

DeferredResult还提供了一些别的属性,如resultHandler可以在设置了结果之后对结果进行处理、timeout设置超时时间、timeoutCallback设置超时处理方法、completionCallback设置处理完成后酌处理方法、timeoutResult设置超时后返回的结果等。

下面看一下Spring MVC中处理DeferredResult返回值的DeferredResultMethodReturnValueHandler处理器,它的handleReturnValue方法如下:

public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
     

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return DeferredResult.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        if (returnValue == null) {
            mavContainer.setRequestHandled(true);
            return;
        }

        DeferredResult deferredResult = (DeferredResult) returnValue;
        WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);
    }

}

这里直接凋用了WebAsyncManager的startDeferredResultProcessing方法进行处理。

下面来看一个返回值为DeferredResult的处理器的例子。

package com.excelib.controller;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Controller
public class AsyncController {
     
    @ResponseBody
    @RequestMapping(value = "/deferred",produces = "text/plain; charset=UTF-8")
    public DeferredResult deferredResultExam() {
        final DeferredResult result = new DeferredResult(7*1000L, "超时了");
        approve(result);
        return result;
    }
    private void approve(DeferredResult result){
        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5 * 1000L);
                    result.setResult("同意 "+new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(r).start();
    }
}

在处理器方法中直接新建了个DeferredResult类型的result代表处理结果,构造方法的两个参数分别表示超时时间和超时后返回的结果,建出来后将其交给approve方法进行处理(审批),当approve方法给result使用setResult方法设置了值后异步处理就完成了。

approve方法启动了一个新线程,然后在里面等待5秒后给result设置值。因为这里的处理器有@ResponseBody注释,所以返回值会直接显示到浏览器,当调用http://localhost:8080/deferred时,浏览器会在过大约5秒后显示同意2015-04-02

现在大家再返回去看WebAsyncManager酌startDeferredResultProcessing方法就容易理解了,它并没有而且也不需要执行,只需要等待别的线程给设置返回值就可以了。方法中给result设置了处理返回值的处理器,当有返回值返回时会自动调用,代码如下:

deferredResult.setResultHandler(new DeferredResultHandler() {
    @Override
    public void handleResult(Object result) {
        result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
        setConcurrentResultAndDispatch(result);
    }
});

这里的处理器中首先调用了拦截器链中的applyPostProcess方法,然后调用setConcurrentResultAndDispatch万法处理了返回值,setConcurrentResultAndDispatch方法前面已经说过了。

现在大家应该对DeferredResult返回值的异步处理就理解了,DeferredResult是一个用于保存返回值的类,只需要在业务处理完成后调用其setResult方法设置结果就可以了,至于怎么处理的、在哪里处理的它并不关心,这也就给我们带来了很大的自由。

22.2.5 ListenableFuture类型异步请求的处理过程及用法

ListenableFuture继承自Future,Future在前面已经介绍过了,它用来保存Callable的处理结果,它提供了get方法来获取返回值,不过Future并不会在处理完成后主动提示。ListenableFuture在Future基础上增加了可以添加处理成功和处理失败回调方法的方法,代码如下:

public interface ListenableFuture<T> extends Future<T> {
     

    void addCallback(ListenableFutureCallbacksuper T> callback);

    void addCallback(SuccessCallbacksuper T> successCallback, FailureCallback failureCallback);

}

ListenableFutureCallback继承自SuccessCallback和FailureCallback接口,后两个接口分别有一个onSuccess方法和onFailure方法,用于处理异步处理成功的返回值和异步处理失败的返回值,就和DeferredResult中的resultHandler差不多,它们定义如下:

public interface ListenableFutureCallback<T> extends SuccessCallback<T>, FailureCallback {
     

}

public interface SuccessCallback<T> {
     

    /**
     * Called when the {@link ListenableFuture} successfully completes.
     * @param result the result
     */
    void onSuccess(T result);

}
public interface FailureCallback {
     

    /**
     * Called when the {@link ListenableFuture} fails to complete.
     * @param ex the exception that triggered the failure
     */
    void onFailure(Throwable ex);

}

ListenableFuture足spring4.0新增的接口,它主要使用在需要调用别的服务的时候,spring还同时提供了AsyncRestTemplate,用它可以方便地发起各种Http请求,不同类型的请求(如Get、Post等)都有不同的方法,而且还可以使用url的模板参数uriVariables(类似于处理器参数中的pathVariables】,它的返回值就是ListenableFuture类型,比如,可以这样使用

ListenableFuture> futureEntity = template.getForEntity(
"http://localhost:8080/students/{studentld}/books/{bookldl" , String.class, "176", "7");

这样就可以返回http://localhost:808 0/students/1 7 6/books/7的Get请求结果,而且是非阻塞的异步调用。

下面看一下处理ListenableFuture返回值的处理器ListenableFutureReturnValueHandler,它的handleReturnValue方法代码如下:

@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    if (returnValue == null) {
        mavContainer.setRequestHandled(true);
        return;
    }

    final DeferredResult deferredResult = new DeferredResult();
    WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);

    ListenableFuture future = (ListenableFuture) returnValue;
    future.addCallback(new ListenableFutureCallback() {
        @Override
        public void onSuccess(Object result) {
            deferredResult.setResult(result);
        }
        @Override
        public void onFailure(Throwable ex) {
            deferredResult.setErrorResult(ex);
        }
    });
} 
  

可以看到在ListenableFuture的返回值处理器里实际使用了DeferredResult.首先新建了DeferredResult类型的deferredResult,接着调用了WebAsyncManager的startDeferredResultProcessing方法进行处理,然后给ListenableFuture类型的返回值添加了回调方法,在回调方法中对deferredResult设置了返回值。可以说ListenableFuture类型的返回值只是DeferredResult类型返回值处理器的一种特殊使用方式。大家好好体会这里的处理过程就可以对DeferredResult跟具体处理过程无关这一点理解得更加深入。

下面来看一个ListenableFuture类型返回值处理器的例子。

package com.excelib.controller;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Controller
public class AsyncController {
     
    @RequestMapping(value = "/listenable",produces = "text/plain; charset=UTF-8")
    public ListenableFuture> listenableFuture() {
        ListenableFuture> future = new AsyncRestTemplate().getForEntity(
                "http://localhost:8080/index", String.class);
        return future;
    }
}

这里处理器的返回值ListenableFuture的泛型是ResponseEntity类型,所以不需要使用@ResponseBody注释也会将返回值直接显示到浏览器。当调用http://localhost:8080/listenable时,浏览器会显示excelibGoGoGo!,也就是http://localhost:8080/index的返回结果.。

多知道点

ListenableFuture和Future的比较

ListenableFuture在Future的基础上增加了可以添加处理成功和处理失败回调方法的方法,这就从Future的“拉”模式变成了ListenableFuture的“推”模式。

Future只能调用get方法来主动拉数据,而且get方法还是阻塞的,而ListenableFuture可以等待处理完成后自己将结果推过来,而且不会阻塞线程,这么看好像ListenableFuture比Future更好用。其实在很多地方Future中阻塞的get方法才是真正需要的,因为很多时候都需要等到线程处理的结果才可以向下进行,比如,要找四个数中最大的那个,可以将四个数分成两组然后启动两个线程分别选出每组中比较大的数,然后再启动一个线程取出两个结果中比较大的,那就是四个数中最大的数,代码如下:

public class ObtainBigger {
     
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        // 需要查找最大数的数组
        Double data[] = new Double[]{
    210.32, 517.96, 986.77, 325.13};
        // 获取前两个里较大的
        BiggerCallable c1 = new BiggerCallable(data[0],data[1]);
        Future bigger1 = executor.submit(c1);
        // 获取后两个里较大的
        BiggerCallable c2 = new BiggerCallable(data[2],data[3]);
        Future bigger2 = executor.submit(c2);
        // 获取两个结果中较大的,这时会阻塞,只有前面两个结果都返回时才会往下进行
        BiggerCallable c = new BiggerCallable(bigger1.get(), bigger2.get());
        Future bigger = executor.submit(c);
        // 输出结果
        System.out.println(bigger.get());
        executor.shutdown();
    }

    private static class BiggerCallable implements Callable {
     
        Double d1, d2;
        public BiggerCallable(Double d1, Double d2){
            this.d1 = d1;
            this.d2 = d2;
        }
        @Override
        public Object call() throws Exception {
            return d1>d2?d1:d2;
        }
    }
}

这里使用了内部类BiggerCallable来比较,第三个BiggerCallable创建时前两个cl)c2必须已经执行完才可以,否则就会出问题,所以在这种情况下阻塞就是必要的,而且这种需要线程返回结果后才能往下进行的情况很多。而ListenableFuture的典型用法就是Web异步请求这种并不需要对线程返回的结果进一步处理,而且线程在返回之前主线程可以继续往下走的情况,这时如果程序阻塞就起不到应有的作用了。

22.3小结

本章系统地介绍了Servlet和SpringMVC中异步处理的原理和使用方法,首先介绍了Servlet3.0中对异步请求的支持及其使用方法,然后又分析了SpringMVC中异步处理的执行过程并编写了示例程序。

Servlet中使用异步请求非常方便,只需要调用request的startAsync方法,然后对其返回值AsyncContext进行处理,如果需要还可以为其添加AsyncListener监听器,它可以监听异步请求的启动、超时、处理完成和处理异常四个节点。

Spring MVC为异步请求提供了专门的工具,并对处理器默认提供了四种用于异步处理的返回值:

1. Callable、
2. WebAsyncTask、
3. DeferredResult
4. ListenableFuture。

对异步请求的支持主要在RequestMappingHandlerAdapter中,启动异步处理在各返回值对应的返回值处理器中。

原文链接:https://github.com/sixtrees/kantouspringmvc/blob/master/%E7%AC%AC22%E7%AB%A0%20%E5%BC%82%E6%AD%A5%E8%AF%B7%E6%B1%82.md

你可能感兴趣的:(Spring,servlet,servlet,spring,mvc,spring,mvc,异步)