Apollo 通過 Spring Mvc DeferredResult 實現長輪詢服務推送

DeferredResult 字面意思就是推遲結果,是在 Servlet 3.0 以後引入了異步請求之後,在 Spring 3.2 版本封裝了一下支持了 Servlet 這個異步請求。DeferredResult 可以允許容器中的線程快速釋放以便可以接受更多的請求提升吞吐量,讓真正的業務邏輯在其他的工作線程中去完成。

最近在看 Apollo 配置中心的實現原理,Apollo 的發佈配置推送變更消息就是用 DeferredResult 實現的。Apollo 客戶端會循環的向服務端發送長輪訓 Http 請求,超時時間 60 秒 。當超時後返回客戶端一個 Http Status304 狀態碼的時候表明配置沒有變更,客戶端繼續這個步驟重複發起請求。當有發佈配置的時候,服務端會調用 DeferredResult.setResult 返回 200 狀態碼,然後輪訓請求會立即返回(不會超時),客戶端收到服務端的響應結果後,會發起向 Apollo 服務端請求獲取變更後的配置信息。

1、模擬 Apollo 通知客戶端消息更新

這裏我們通過使用 Spring Boot 來簡單的模擬一下 Apollo 是如何通過 Spring MVC DeferredResult 來實現長輪詢服務推送的。

1.1 Bootstrap.java

Bootstrap 類做爲 Spring Boot 啓動的啓動類,通過實現 WebMvcConfigurer 配置 Spring MVC 支持異步的線程池。設置了一個用來異步執行業務邏輯的工作線程池,設置了默認的超時時間是 60 秒。

@SpringBootApplication
public class Bootstrap implements WebMvcConfigurer {
 
	public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
 
    @Bean
    public ThreadPoolTaskExecutor mvcTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setQueueCapacity(100);
        executor.setMaxPoolSize(25);
        return executor;
 
    }
 
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setTaskExecutor(mvcTaskExecutor());
        configurer.setDefaultTimeout(60000L);
    }
}

1.2 GlobalExceptionHandler.java

GlobalExceptionHandler 定義 Spring MVC 全局異常攔截類。當 Spring Mvc DeferredResult 處理請求的時候,如果超時沒有響應結果就會拋出 AsyncRequestTimeoutException 異常。通過攔截此異常,返回 304 狀態碼告訴客戶端當前命名空間的配置文件並沒有更新。

@ControllerAdvice
class GlobalExceptionHandler {

    protected static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ResponseStatus(HttpStatus.NOT_MODIFIED)//返回304狀態碼
    @ResponseBody
    @ExceptionHandler(AsyncRequestTimeoutException.class) //捕獲特定異常
    public void handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e, HttpServletRequest request) {
        logger.info("handleAsyncRequestTimeoutException");
    }
}

1.3 測試 Controller 返回 DeferredResult

@RestController
public class ApolloController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    //guava中的Multimap,多值map,對map的增強,一個key可以保持多個value
    private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());
 
 
    //模擬長輪詢
    @RequestMapping(value = "/watch/{namespace}", method = RequestMethod.GET, produces = "text/html")
    public DeferredResult<String> watch(@PathVariable("namespace") String namespace) {
        logger.info("Request received");
        DeferredResult<String> deferredResult = new DeferredResult<>();
        //當deferredResult完成時(不論是超時還是異常還是正常完成),移除watchRequests中相應的watch key
        deferredResult.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("remove key:" + namespace);
                watchRequests.remove(namespace, deferredResult);
            }
        });
        watchRequests.put(namespace, deferredResult);
        logger.info("Servlet thread released");
        return deferredResult;
 
 
    }
 
    //模擬發佈namespace配置
    @RequestMapping(value = "/publish/{namespace}", method = RequestMethod.GET, produces = "text/html")
    public Object publishConfig(@PathVariable("namespace") String namespace) {
        if (watchRequests.containsKey(namespace)) {
            Collection<DeferredResult<String>> deferredResults = watchRequests.get(namespace);
            Long time = System.currentTimeMillis();
            //通知所有watch這個namespace變更的長輪訓配置變更結果
            for (DeferredResult<String> deferredResult : deferredResults) {
                deferredResult.setResult(namespace + " changed:" + time);
            }
        }
        return "success";
 
    }
}

首先我們通過 Postman 請求地址 http://localhost:8080/watch/123 不做其它操作,請求會被掛起,等待 60 秒後,會拋出 AsyncRequestTimeoutException 異常,全局異常捕獲會返回 302 狀態碼。這樣就表示期間配置沒有被更改過。

然後我們再次通過 Postman 請求地址 http://localhost:8080/watch/123,在超時時間(60 s) 之前請求 http://localhost:8080/publish/123 表示配置變更。這時 watch 地址會立即返回,並且狀態碼爲 200,這時客戶端就根據狀態碼就知道配置變更了。

使用用了一個MultiMap來存放所有輪訓的請求,Key 對應的是 namespace ,value 對應的是所有 watch 這個 namespace 變更的異步請求DeferredResult,需要注意的是:在DeferredResult 完成的時候記得移除 MultiMap中 相應的 key ,避免內存溢出請求。採用這種長輪詢的好處是,相比一直循環請求服務器,實例一多的話會對服務器產生很大的壓力,http長輪詢的方式會在服務器變更的時候主動推送給客戶端,其他時間客戶端是掛起請求的,這樣同時滿足了性能和實時性。

2、Servlet 3.0 AsyncListener 異步支持

在Servlet 3.0版本之前,Servlet線程需要一直阻塞,直到業務處理完畢才能再輸出響應,最後才結束該Servlet線程。而有了異步處理特性,Servlet線程不再需要一直阻塞,在接收到請求之後,Servlet線程可以將耗時的操作委派給另一個線程來完成,自己在不生成響應的情況下返回至容器。針對業務處理較耗時的情況,這可以大幅度降低服務器的資源消耗,並且提高併發處理速度。

在 Servlet 3.0 之後新增了一組接口 AsyncListener、AsyncContext 與 AsyncContextCallback 用於支持異步處理,下面我們就針對接口來分析一下 Sevlet 是如何實現異步支持的。

AsyncListener.java

public interface AsyncListener extends EventListener {

		// 異步處理完成執行
    void onComplete(AsyncEvent event) throws IOException;
    
    // 異步處理超時執行
    void onTimeout(AsyncEvent event) throws IOException;
    
    // 異步處理錯誤執行
    void onError(AsyncEvent event) throws IOException;
    
    // 異步處理開始時執行
    void onStartAsync(AsyncEvent event) throws IOException;
    
}

實現了 java.util.EventListener ,通過實現它來達到異步處理的目的。裏面定義了 4 個方法,分別對應異步處理執行不同的狀態後的回調執行方法。

AsyncContext.java

public interface AsyncContext {

    ServletRequest getRequest();

    ServletResponse getResponse();

    void dispatch();

    void dispatch(String path);

    void dispatch(ServletContext context, String path);

    void complete();

    void start(Runnable run);

    void addListener(AsyncListener listener);

    void addListener(AsyncListener listener, ServletRequest request, ServletResponse response);

    void setTimeout(long timeout);

    long getTimeout();
    
}

實現AsyncContext接口的對象可以通過startAsync方法從ServletRequest獲得。有了AsyncContext後,可以使用它通過complete()方法完成對請求的處理,也可以使用下面描述的其中的一個 dispatch 方法。

下面的方法可以用來分發從 AsyncContext 的請求:

  • dispatch(path) :這個方法接受一個字符串參數,該參數描述 ServletContext 作用域內的路徑。此路徑必須相對於 ServletContext 的根,並以’ / '開頭。
  • dispatch(servletContext, path):這個方法方法接受一個字符串參數,該參數描述指定的 ServletContext 作用域內的路徑。此路徑必須相對於指定的 ServletContext 的根,並以’ / '開頭。
  • dispatch():這個方法不帶參數。它使用原始 URI 作爲路徑。如果 AsyncContext 是通過 startAsync(ServletRequest, ServletResponse)初始化的,並且傳遞的請求是 HttpServletRequest 的一個實例,那麼分派到 HttpServletRequest. getrequesturi() 返回的 URI。否則,該分派將在容器最後一次分派請求時發送到該請求的 URI .

在等待異步事件發生時,應用程序可以調用 AsyncContext 接口的一個分派方法。如果在 AsyncContext 上調用了complete(),則必須拋出 IllegalStateException。分派方法的所有變體都立即返回,並且不提交響應。

公開給目標 servlet 的請求對象的路徑元素必須反映 AsyncContext.dispatch 中指定的路徑。

AsyncContextCallback.java

public interface AsyncContextCallback {

    public void fireOnComplete();

    /**
     * Reports if the web application associated with this async request is
     * available.
     *
     * @return {@code true} if the associated web application is available,
     *         otherwise {@code false}
     */
    public boolean isAvailable();
}

AsyncContextCallback 是一個回調函數,它包裝了多個 AsyncListener,用於執行異步事件完成後調用 AsyncListener#onComplete

注意:Servlet 3.0 異步處理的整個流程包括以下兩個步驟:

  • 調用 ServletRequest#startAsync 開始 Servlet 裏面的異步執行
  • 調用 AsyncContext#dispatch 完成整個異步處理

3、DeferredResult 實現原理

在 Spring Mvc 3.2 後就可以使用 Servlet 3.0 之後的異步處理,對於 Spring mvc 框架使用者,我們只需要返回值定義爲 DeferredResult 就可以實現這個功能,可以參考章節 1 的例子。下面我們就來看一下 Spring mvc 是如何集成 Servlet 3.0 中的異步處理的。

3.1 StandardServletAsyncWebRequest.java

實現 javax.servlet.AsyncListener 接口,集成 Serlvet 3.0 異步處理。同時也實現了 Spring 自定義的接口 org.springframework.web.context.request.async.AsyncWebRequest 。這個接口抽象了 Servlet 3.0 javax.servlet.AsyncContext 裏面的異步t處理的核心方法。

AsyncWebRequest.java

public interface AsyncWebRequest extends NativeWebRequest {

  // 設置超時時間
	void setTimeout(@Nullable Long timeout);

  // 設置異步處理超時間處理類
	void addTimeoutHandler(Runnable runnable);

  // 設置異步處理異常處理類
	void addErrorHandler(Consumer<Throwable> exceptionHandler);

  // 設置異步處理完成處理類
	void addCompletionHandler(Runnable runnable);
  
  // 開始異步處理
	void startAsync();

  // 是否異步處理開始
	boolean isAsyncStarted();

  // 分發請求
	void dispatch();

  // 是否異步處理完成
	boolean isAsyncComplete();

}

這其實也是 Spring 的一種編程思想,對外部接口進行接口抽象,當 Servlet 容器替換的時候,只需要實現另外一種 Servlet 容器就可以了,對於 Spring Mvc 內部調用異步處理的操作並沒有修改。

3.2 WebAsyncUtils.java

這個是用於關聯處理異步 Web 請求的工具類,從 Spring Mvc 3.2 版本開始就有這個類了。

/**
 * Utility methods related to processing asynchronous web requests.
 *
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @since 3.2
 */
public abstract class WebAsyncUtils {

	public static final String WEB_ASYNC_MANAGER_ATTRIBUTE =
			WebAsyncManager.class.getName() + ".WEB_ASYNC_MANAGER";

	public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
		WebAsyncManager asyncManager = null;
		Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
		if (asyncManagerAttr instanceof WebAsyncManager) {
			asyncManager = (WebAsyncManager) asyncManagerAttr;
		}
		if (asyncManager == null) {
			asyncManager = new WebAsyncManager();
			servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
		}
		return asyncManager;
	}

	public static WebAsyncManager getAsyncManager(WebRequest webRequest) {
		int scope = RequestAttributes.SCOPE_REQUEST;
		WebAsyncManager asyncManager = null;
		Object asyncManagerAttr = webRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, scope);
		if (asyncManagerAttr instanceof WebAsyncManager) {
			asyncManager = (WebAsyncManager) asyncManagerAttr;
		}
		if (asyncManager == null) {
			asyncManager = new WebAsyncManager();
			webRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager, scope);
		}
		return asyncManager;
	}

	public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
		return new StandardServletAsyncWebRequest(request, response);
	}

}

它的裏面有三個核心的方法:

  • getAsyncManager(servletRequest):通過 ServletRequest (原生 Servlet 請求 API)獲取到 WebAsyncManager ,它是用於 Web 異步管理的
  • getAsyncManager(webRequest):通過 WebRequest (Spring Mvc 封裝的 Servlet 請求接口)獲取到 WebAsyncManager ,它是用於 Web 異步管理的
  • createAsyncWebRequest(request, response) :創建 AsyncWebRequest 接口實例,也就是上面的 StandardServletAsyncWebRequest 對象,它實現了 Servlet 3.0 javax.servlet.AsyncContext ,可以對處理異步是對異步請求的一個包裝。

WebAsyncManager 這個類非常重要,我們後續的流程中來具體的分析它。

3.3 Spring Mvc 處理異步流程

在這裏並不會完整的講解 Spring Mvc 處理 Http 請求的完整請求,只會說明 Spring Mvc 在整個流程裏面處理異步請求的差異點。如果對 Spring Mvc 處理 Http 的完整流程不太清楚可以觀看博主之前寫的 – Spring MVC DispatcherServlet

3.3.1 調用 Controller 之前添加 WebAsyncManager

Spring MVC 處理每次 http 請求的時候都會調用以下方法爲當前 ServletRequest 設置 WebAsyncManager。每個 WebAsyncManager 是和一次 ServletRequest 綁定的。

WebAsyncUtils#getAsyncManager(javax.servlet.ServletRequest)

servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);

它的調用入口是 RequestMappingHandlerAdapter#invokeHandlerMethod

RequestMappingHandlerAdapter#invokeHandlerMethod

	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

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

      // 爲當前請求綁定 WebAsyncManager
			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();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

3.3.2 DeferredResult 返回值處理

這個方法之後就是調用 URI 對應的 Spring Mvc Controller 裏面的處理類,因爲 Controller 裏面的 RequestMapping 方法的返回值是 DeferredResult ,所以它對應的返回值處理類是 DeferredResultMethodReturnValueHandler ,在這個返回值處理類當中,它的核心代碼是:

DeferredResultMethodReturnValueHandler#handleReturnValue

WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);

通過 WebAsyncUtils#getAsyncManager(org.springframework.web.context.request.WebRequest),獲取到之前在調用 Controller 之前設置的 WebAsyncManager 通過 WebAsyncManager#startDeferredResultProcessing 來進行異步處理。下面我們就來看一下 WebAsyncManager 是如何進行異步調用的。

3.3.3 WebAsyncManager 處理異步請求

上面已經說過,在 DeferredResult 的返回值處理器中會調用WebAsyncManager#startDeferredResultProcessing來進行異步調用,下面我們就來分析一下這個方法中是如何處理的。

WebAsyncManager#startDeferredResultProcessing

public void startDeferredResultProcessing(
			final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {

		Assert.notNull(deferredResult, "DeferredResult must not be null");
		Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");

		Long timeout = deferredResult.getTimeoutValue();
		if (timeout != null) {
			this.asyncWebRequest.setTimeout(timeout);
		}

		List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<>();
		interceptors.add(deferredResult.getInterceptor());
		interceptors.addAll(this.deferredResultInterceptors.values());
		interceptors.add(timeoutDeferredResultInterceptor);

		final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);

		this.asyncWebRequest.addTimeoutHandler(() -> {
			try {
				interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult);
			}
			catch (Throwable ex) {
				setConcurrentResultAndDispatch(ex);
			}
		});

		this.asyncWebRequest.addErrorHandler(ex -> {
			try {
				if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) {
					return;
				}
				deferredResult.setErrorResult(ex);
			}
			catch (Throwable interceptorEx) {
				setConcurrentResultAndDispatch(interceptorEx);
			}
		});

		this.asyncWebRequest.addCompletionHandler(()
				-> interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult));

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

		try {
			interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
			deferredResult.setResultHandler(result -> {
				result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
				setConcurrentResultAndDispatch(result);
			});
		}
		catch (Throwable ex) {
			setConcurrentResultAndDispatch(ex);
		}
	}

上面這個方法代碼看着挺多其實做的事情如下:

  • AsyncWebRequest.setTimeout(timeout):如果 DeferredResult 設置了超時時間,就設置異步調用的超時時間,一般使用 使用 AsyncSupportConfigurer 配置異步執行線程池與超時時間,如章節 1 的示例。
  • AsyncWebRequest.addTimeoutHandler(runnable):添加一個超時時間處理類,默認使用 TimeoutDeferredResultProcessingInterceptor,它在異步調用超時時會拋出 AsyncRequestTimeoutException 異常。同時也會會調用 DeferredResultProcessingInterceptor#handleTimeout 鏈,默認爲空。
  • AsyncWebRequest.addErrorHandler(runnable):添加一個異步執行異常處理類,會調用 DeferredResultProcessingInterceptor#handleError 方法鏈,默認爲空。
  • AsyncWebRequest.addCompletionHandler(runnable):添加一個異步執行完成處理類DeferredResultProcessingInterceptor#afterCompletion 方法鏈,默認爲空。
  • 調用 DeferredResultProcessingInterceptor#beforeConcurrentHandling方法鏈默認爲空實現。
  • 開始異步執行 WebAsyncManager#startAsyncProcessing ,調用 AsyncWebRequest#startAsync 接口開始異步處理
  • 調用 DeferredResultInterceptorChain#applyPreProcess 方法鏈,默認爲空。
  • 調用 DeferredResult#setResultHandler 方法,設置當 DeferredResult 異步設置返回值時的處理方法,核心方法。

上面的方法很多,最核心的是兩個方法。

  • WebAsyncManager#startAsyncProcessing,調用 AsyncWebRequest#startAsync 接口開始異步處理,在這裏他用調用 AsyncWebRequest 對象的實例 StandardServletAsyncWebRequest#startAsync,它即實現了 Spring 提供的異步請求處理接口 AsyncWebRequest 又實現了javax.servlet.AsyncListener,集成了 Servlet 3.0 的處理。它會把之前設置的回調方法添加到 javax.servlet.AsyncContext 當中。並且執行 ServletRequest#startAsync 開始 Servlet 裏面的異步執行。
  • 調用 DeferredResult#setResultHandler 方法,設置當 DeferredResult 異步設置返回值時的處理方法,核心方法。它會調用 WebAsyncManager#setConcurrentResultAndDispatch ,然後會調用 StandardServletAsyncWebRequest#dispatch ,最終會調用到 AsyncContext.dispatch()

3.3.4 Spring Mvc 的實現方式

以實現 Servlet 3.0 的 Spring 舉例,異步操作其實是基於事件的處理。當調用 ServletRequest#startAsync 會有一個開始處理的事件,然後調用 AsyncContext.dispatch() 異步請求完成,會完成當前的請求。

在 Controller 的 RequestMapping 方法中定義一個 DeferredResult 響應對象, Spring Mvc 的返回值處理器會調用 ServletRequest#startAsync 的方法,設置一系統的回調方法(不是重點),核心是以下代碼:

WebAsyncManager#startDeferredResultProcessing

deferredResult.setResultHandler(result -> {
	result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
	setConcurrentResultAndDispatch(result);
});

// 調用 AsyncContext.dispatch() 完成整個異步處理
private void setConcurrentResultAndDispatch(Object result) {
	synchronized (WebAsyncManager.this) {
		if (this.concurrentResult != RESULT_NONE) {
			return;
		}
		this.concurrentResult = result;
	}

	if (this.asyncWebRequest.isAsyncComplete()) {
		if (logger.isDebugEnabled()) {
			logger.debug("Async result set but request already complete: " + formatRequestUri());
		}
		return;
	}

	if (logger.isDebugEnabled()) {
		boolean isError = result instanceof Throwable;
		logger.debug("Async " + (isError ? "error" : "result set") + ", dispatch to " + formatRequestUri());
	}
	this.asyncWebRequest.dispatch();
}

所以當調用 DeferredResult.setResult 方法就會觸發 AsyncContext.dispatch() 方法調用, Servlet 異步處理的回調,這時 Tomcat 就會響應結果,完成整個方法的調用。

參考文章:

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