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 extends AsyncListener> T createListener(Class<T> 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。

<servlet>
    <servlet-name>WorkServlet</servlet-name>
    <servlet-class>com.excelib.servlet.WorkServlet</servlet-class>
    <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
    <servlet-name>WorkServlet</servlet-name>
    <url-pattern>/work</url-pattern>
</servlet-mapping>

然後新建一個叫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<String> 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<String> 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<Runnable> timeoutHandlers = new ArrayList<Runnable>();

    private final List<Runnable> completionHandlers = new ArrayList<Runnable>();


    /**
     * 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}
     * <p>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.
     * <p>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 \"<async-supported>true</async-supported>\" 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();

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

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

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

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

    <T> void afterCompletion(NativeWebRequest request, Callable<T> 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<V> callable;

    private Long timeout;

    private AsyncTaskExecutor executor;

    private String executorName;

    private BeanFactory beanFactory;

    private Callable<V> 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 <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> 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<String>() {
    public String call() throws Exception{
        Thread.sleep(1000);
        System.out.println("do task");
        return "ok";
    }
};
Future<String> 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<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
        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 <T> void preProcess(NativeWebRequest webRequest, Callable<T> 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 <T> void postProcess(NativeWebRequest webRequest, Callable<T> 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<Object>() {
            @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<Foo>}.
     */
    @Override
    public MethodParameter getReturnValueType(Object returnValue) {
        return this.returnType;
    }

    /**
     * Bridge to controller method-level annotations.
     */
    @Override
    public <A extends Annotation> A getMethodAnnotation(Class<A> 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'sGo</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/let'sGo-servlet.xml</param-value>
    </init-param>
    <async-supported>true</async-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<String> webAsyncTask(){
        System.out.println("WebAsyncTask處理器主線程進入");
        WebAsyncTask<String> task = new WebAsyncTask<String>(new Callable<String>() {
            @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<String> callable(){
        System.out.println("Callable處理器主線程進入");
        Callable<String> callable = new Callable<String>() {
            @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<String> deferredResultExam() {
        final DeferredResult<String> result = new DeferredResult<String>(7*1000L, "超時了");
        approve(result);
        return result;
    }
    private void approve(DeferredResult<String> 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(ListenableFutureCallback<? super T> callback);

    void addCallback(SuccessCallback<? super 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<ResponseEntity<String>> 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<Object> deferredResult = new DeferredResult<Object>();
    WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(deferredResult, mavContainer);

    ListenableFuture<?> future = (ListenableFuture<?>) returnValue;
    future.addCallback(new ListenableFutureCallback<Object>() {
        @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<ResponseEntity<String>> listenableFuture() {
        ListenableFuture<ResponseEntity<String>> 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<Double> bigger1 = executor.submit(c1);
        // 獲取後兩個裏較大的
        BiggerCallable c2 = new BiggerCallable(data[2],data[3]);
        Future<Double> bigger2 = executor.submit(c2);
        // 獲取兩個結果中較大的,這時會阻塞,只有前面兩個結果都返回時纔會往下進行
        BiggerCallable c = new BiggerCallable(bigger1.get(), bigger2.get());
        Future<Double> 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章