Spring长轮询DeferredResult简单用法以及SpringMVC对于后置结果处理
简单研究下spring 长轮训 DeferredResult 的用法以及简单的原理。 如果让自己设计,可能就是会用一些异步+spring的扩展机制来实现。
1. DeferredResult简单用法
1. 新建测试类
package cn.qz.template.controller; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.UUID; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import java.util.Date; import java.util.Random; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @RestController public class AsycnController { // 维护队列 private ConcurrentLinkedDeque<DeferredResult<String>> deferredResults = new ConcurrentLinkedDeque<DeferredResult<String>>(); // 处理线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactoryBuilder().setNameFormat("custom-pool-%d").build()); @RequestMapping("/getResult") @ResponseBody public DeferredResult<String> getDeferredResultController() { final String requestId = UUID.fastUUID().toString(true); printStr("收到请求\t" + requestId); final String message = "defaultValue" + requestId; // 设置 5秒就会超时 final DeferredResult<String> stringDeferredResult = new DeferredResult<String>(3000L); // 也可以直接设置默认值 // final DeferredResult<String> stringDeferredResult1 = new DeferredResult<String>(3000L, message); //将请求加入到队列中 deferredResults.add(stringDeferredResult); // 正常处理 executorService.submit(new Runnable() { @Override public void run() { try { int time = new Random().nextInt(10) * 1000; printStr("休眠: " + time + "\t" + requestId); Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } //业务处理 printStr("业务处理完成\t" + requestId); stringDeferredResult.setResult(message); } }); // setResult完毕之后,调用该方法 stringDeferredResult.onCompletion(new Runnable() { @Override public void run() { printStr("异步调用完成\t" + requestId); //响应完毕之后,将请求从队列中去除掉 deferredResults.remove(stringDeferredResult); } }); stringDeferredResult.onTimeout(new Runnable() { @Override public void run() { printStr("业务处理超时\t" + requestId); stringDeferredResult.setResult("error:timeOut\t" + requestId); } }); return stringDeferredResult; } private void printStr(String str) { System.out.println(DateUtil.format(new Date(), "HH:mm:ss") + "\tThreadName :" + Thread.currentThread().getName() + "\t" + str); } }
2. 测试后查看日志:
第一次:
2023-01-08 15:17:28.429 | [http-nio-8090-exec-7] INFO cn.qz.template.config.ControllerAspect - 请求方法:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController() 15:17:28 ThreadName :http-nio-8090-exec-7 收到请求 5b3753150abf4a16b16c1e5ac9d2f932 2023-01-08 15:17:28.431 | [http-nio-8090-exec-7] INFO cn.qz.template.config.ControllerAspect - 请求完毕:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController()=>org.springframework.web.context.request.async.DeferredResult@69317f18 15:17:28 ThreadName :custom-pool-2 休眠: 5000 5b3753150abf4a16b16c1e5ac9d2f932 15:17:32 ThreadName :http-nio-8090-exec-8 业务处理超时 5b3753150abf4a16b16c1e5ac9d2f932 15:17:32 ThreadName :http-nio-8090-exec-8 异步调用完成 5b3753150abf4a16b16c1e5ac9d2f932 15:17:33 ThreadName :custom-pool-2 业务处理完成 5b3753150abf4a16b16c1e5ac9d2f932
第二次
2023-01-08 15:18:39.470 | [http-nio-8090-exec-1] INFO cn.qz.template.config.ControllerAspect - 请求方法:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController() 15:18:39 ThreadName :http-nio-8090-exec-1 收到请求 0bf4f5cad145401f9aa8e4a9e37fede0 2023-01-08 15:18:39.474 | [http-nio-8090-exec-1] INFO cn.qz.template.config.ControllerAspect - 请求完毕:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController()=>org.springframework.web.context.request.async.DeferredResult@42ff9f36 15:18:39 ThreadName :custom-pool-3 休眠: 2000 0bf4f5cad145401f9aa8e4a9e37fede0 15:18:41 ThreadName :custom-pool-3 业务处理完成 0bf4f5cad145401f9aa8e4a9e37fede0 15:18:41 ThreadName :http-nio-8090-exec-2 异步调用完成 0bf4f5cad145401f9aa8e4a9e37fede0
第三次:
2023-01-08 15:21:20.260 | [http-nio-8090-exec-7] INFO cn.qz.template.config.ControllerAspect - 请求方法:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController() 15:21:20 ThreadName :http-nio-8090-exec-7 收到请求 3bad228d702b40a29f1188010aaa15c6 2023-01-08 15:21:20.261 | [http-nio-8090-exec-7] INFO cn.qz.template.config.ControllerAspect - 请求完毕:DeferredResult cn.qz.template.controller.AsycnController.getDeferredResultController()=>org.springframework.web.context.request.async.DeferredResult@1dd50481 15:21:20 ThreadName :custom-pool-5 休眠: 1000 3bad228d702b40a29f1188010aaa15c6 15:21:21 ThreadName :custom-pool-5 业务处理完成 3bad228d702b40a29f1188010aaa15c6 15:21:21 ThreadName :http-nio-8090-exec-8 异步调用完成 3bad228d702b40a29f1188010aaa15c6
3. 总结:
1》可以看到,对于spring 框架调度来说,是先反射调用方法,然后返回结果。但是对于前端来说请求还是阻塞的,没有拿到结果
2》从线程的角度来说,业务处理是用的自己的线程池,onCompletion 完成和onTimeout 超时是用的tomcat 的线程池。
补充: 如果超时没有setResult,客户端会收到响应码为503的状态码-服务不可达
比如代码:
stringDeferredResult.onTimeout(new Runnable() { @Override public void run() { printStr("业务处理超时\t" + requestId); // stringDeferredResult.setResult("error:timeOut\t" + requestId); } });
结果:
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Thu Feb 16 18:07:48 CST 2023 There was an unexpected error (type=Service Unavailable, status=503).
补充:也可以对 DeferredResult 设置超时的默认值
设置默认值之后再onTimeout 里面就不用在手动setResult
final DeferredResult<String> stringDeferredResult = new DeferredResult<String>(3000L, "超时默认值"); 。。。 stringDeferredResult.onTimeout(new Runnable() { @Override public void run() { printStr("业务处理超时\t" + requestId); } });
查看源码发现是在:
org.springframework.web.context.request.async.DeferredResult#getInterceptor
1 /* 2 * Copyright 2002-2020 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.web.context.request.async; 18 19 import java.util.PriorityQueue; 20 import java.util.concurrent.Callable; 21 import java.util.function.Consumer; 22 import java.util.function.Supplier; 23 24 import org.apache.commons.logging.Log; 25 import org.apache.commons.logging.LogFactory; 26 27 import org.springframework.lang.Nullable; 28 import org.springframework.util.Assert; 29 import org.springframework.web.context.request.NativeWebRequest; 30 31 /** 32 * {@code DeferredResult} provides an alternative to using a {@link Callable} for 33 * asynchronous request processing. While a {@code Callable} is executed concurrently 34 * on behalf of the application, with a {@code DeferredResult} the application can 35 * produce the result from a thread of its choice. 36 * 37 * <p>Subclasses can extend this class to easily associate additional data or behavior 38 * with the {@link DeferredResult}. For example, one might want to associate the user 39 * used to create the {@link DeferredResult} by extending the class and adding an 40 * additional property for the user. In this way, the user could easily be accessed 41 * later without the need to use a data structure to do the mapping. 42 * 43 * <p>An example of associating additional behavior to this class might be realized 44 * by extending the class to implement an additional interface. For example, one 45 * might want to implement {@link Comparable} so that when the {@link DeferredResult} 46 * is added to a {@link PriorityQueue} it is handled in the correct order. 47 * 48 * @author Rossen Stoyanchev 49 * @author Juergen Hoeller 50 * @author Rob Winch 51 * @since 3.2 52 * @param <T> the result type 53 */ 54 public class DeferredResult<T> { 55 56 private static final Object RESULT_NONE = new Object(); 57 58 private static final Log logger = LogFactory.getLog(DeferredResult.class); 59 60 61 @Nullable 62 private final Long timeoutValue; 63 64 private final Supplier<?> timeoutResult; 65 66 private Runnable timeoutCallback; 67 68 private Consumer<Throwable> errorCallback; 69 70 private Runnable completionCallback; 71 72 private DeferredResultHandler resultHandler; 73 74 private volatile Object result = RESULT_NONE; 75 76 private volatile boolean expired; 77 78 79 /** 80 * Create a DeferredResult. 81 */ 82 public DeferredResult() { 83 this(null, () -> RESULT_NONE); 84 } 85 86 /** 87 * Create a DeferredResult with a custom timeout value. 88 * <p>By default not set in which case the default configured in the MVC 89 * Java Config or the MVC namespace is used, or if that's not set, then the 90 * timeout depends on the default of the underlying server. 91 * @param timeoutValue timeout value in milliseconds 92 */ 93 public DeferredResult(Long timeoutValue) { 94 this(timeoutValue, () -> RESULT_NONE); 95 } 96 97 /** 98 * Create a DeferredResult with a timeout value and a default result to use 99 * in case of timeout. 100 * @param timeoutValue timeout value in milliseconds (ignored if {@code null}) 101 * @param timeoutResult the result to use 102 */ 103 public DeferredResult(@Nullable Long timeoutValue, Object timeoutResult) { 104 this.timeoutValue = timeoutValue; 105 this.timeoutResult = () -> timeoutResult; 106 } 107 108 /** 109 * Variant of {@link #DeferredResult(Long, Object)} that accepts a dynamic 110 * fallback value based on a {@link Supplier}. 111 * @param timeoutValue timeout value in milliseconds (ignored if {@code null}) 112 * @param timeoutResult the result supplier to use 113 * @since 5.1.1 114 */ 115 public DeferredResult(@Nullable Long timeoutValue, Supplier<?> timeoutResult) { 116 this.timeoutValue = timeoutValue; 117 this.timeoutResult = timeoutResult; 118 } 119 120 121 /** 122 * Return {@code true} if this DeferredResult is no longer usable either 123 * because it was previously set or because the underlying request expired. 124 * <p>The result may have been set with a call to {@link #setResult(Object)}, 125 * or {@link #setErrorResult(Object)}, or as a result of a timeout, if a 126 * timeout result was provided to the constructor. The request may also 127 * expire due to a timeout or network error. 128 */ 129 public final boolean isSetOrExpired() { 130 return (this.result != RESULT_NONE || this.expired); 131 } 132 133 /** 134 * Return {@code true} if the DeferredResult has been set. 135 * @since 4.0 136 */ 137 public boolean hasResult() { 138 return (this.result != RESULT_NONE); 139 } 140 141 /** 142 * Return the result, or {@code null} if the result wasn't set. Since the result 143 * can also be {@code null}, it is recommended to use {@link #hasResult()} first 144 * to check if there is a result prior to calling this method. 145 * @since 4.0 146 */ 147 @Nullable 148 public Object getResult() { 149 Object resultToCheck = this.result; 150 return (resultToCheck != RESULT_NONE ? resultToCheck : null); 151 } 152 153 /** 154 * Return the configured timeout value in milliseconds. 155 */ 156 @Nullable 157 final Long getTimeoutValue() { 158 return this.timeoutValue; 159 } 160 161 /** 162 * Register code to invoke when the async request times out. 163 * <p>This method is called from a container thread when an async request 164 * times out before the {@code DeferredResult} has been populated. 165 * It may invoke {@link DeferredResult#setResult setResult} or 166 * {@link DeferredResult#setErrorResult setErrorResult} to resume processing. 167 */ 168 public void onTimeout(Runnable callback) { 169 this.timeoutCallback = callback; 170 } 171 172 /** 173 * Register code to invoke when an error occurred during the async request. 174 * <p>This method is called from a container thread when an error occurs 175 * while processing an async request before the {@code DeferredResult} has 176 * been populated. It may invoke {@link DeferredResult#setResult setResult} 177 * or {@link DeferredResult#setErrorResult setErrorResult} to resume 178 * processing. 179 * @since 5.0 180 */ 181 public void onError(Consumer<Throwable> callback) { 182 this.errorCallback = callback; 183 } 184 185 /** 186 * Register code to invoke when the async request completes. 187 * <p>This method is called from a container thread when an async request 188 * completed for any reason including timeout and network error. This is useful 189 * for detecting that a {@code DeferredResult} instance is no longer usable. 190 */ 191 public void onCompletion(Runnable callback) { 192 this.completionCallback = callback; 193 } 194 195 /** 196 * Provide a handler to use to handle the result value. 197 * @param resultHandler the handler 198 * @see DeferredResultProcessingInterceptor 199 */ 200 public final void setResultHandler(DeferredResultHandler resultHandler) { 201 Assert.notNull(resultHandler, "DeferredResultHandler is required"); 202 // Immediate expiration check outside of the result lock 203 if (this.expired) { 204 return; 205 } 206 Object resultToHandle; 207 synchronized (this) { 208 // Got the lock in the meantime: double-check expiration status 209 if (this.expired) { 210 return; 211 } 212 resultToHandle = this.result; 213 if (resultToHandle == RESULT_NONE) { 214 // No result yet: store handler for processing once it comes in 215 this.resultHandler = resultHandler; 216 return; 217 } 218 } 219 // If we get here, we need to process an existing result object immediately. 220 // The decision is made within the result lock; just the handle call outside 221 // of it, avoiding any deadlock potential with Servlet container locks. 222 try { 223 resultHandler.handleResult(resultToHandle); 224 } 225 catch (Throwable ex) { 226 logger.debug("Failed to process async result", ex); 227 } 228 } 229 230 /** 231 * Set the value for the DeferredResult and handle it. 232 * @param result the value to set 233 * @return {@code true} if the result was set and passed on for handling; 234 * {@code false} if the result was already set or the async request expired 235 * @see #isSetOrExpired() 236 */ 237 public boolean setResult(T result) { 238 return setResultInternal(result); 239 } 240 241 private boolean setResultInternal(Object result) { 242 // Immediate expiration check outside of the result lock 243 if (isSetOrExpired()) { 244 return false; 245 } 246 DeferredResultHandler resultHandlerToUse; 247 synchronized (this) { 248 // Got the lock in the meantime: double-check expiration status 249 if (isSetOrExpired()) { 250 return false; 251 } 252 // At this point, we got a new result to process 253 this.result = result; 254 resultHandlerToUse = this.resultHandler; 255 if (resultHandlerToUse == null) { 256 // No result handler set yet -> let the setResultHandler implementation 257 // pick up the result object and invoke the result handler for it. 258 return true; 259 } 260 // Result handler available -> let's clear the stored reference since 261 // we don't need it anymore. 262 this.resultHandler = null; 263 } 264 // If we get here, we need to process an existing result object immediately. 265 // The decision is made within the result lock; just the handle call outside 266 // of it, avoiding any deadlock potential with Servlet container locks. 267 resultHandlerToUse.handleResult(result); 268 return true; 269 } 270 271 /** 272 * Set an error value for the {@link DeferredResult} and handle it. 273 * The value may be an {@link Exception} or {@link Throwable} in which case 274 * it will be processed as if a handler raised the exception. 275 * @param result the error result value 276 * @return {@code true} if the result was set to the error value and passed on 277 * for handling; {@code false} if the result was already set or the async 278 * request expired 279 * @see #isSetOrExpired() 280 */ 281 public boolean setErrorResult(Object result) { 282 return setResultInternal(result); 283 } 284 285 286 final DeferredResultProcessingInterceptor getInterceptor() { 287 return new DeferredResultProcessingInterceptor() { 288 @Override 289 public <S> boolean handleTimeout(NativeWebRequest request, DeferredResult<S> deferredResult) { 290 boolean continueProcessing = true; 291 try { 292 if (timeoutCallback != null) { 293 timeoutCallback.run(); 294 } 295 } 296 finally { 297 Object value = timeoutResult.get(); 298 if (value != RESULT_NONE) { 299 continueProcessing = false; 300 try { 301 setResultInternal(value); 302 } 303 catch (Throwable ex) { 304 logger.debug("Failed to handle timeout result", ex); 305 } 306 } 307 } 308 return continueProcessing; 309 } 310 @Override 311 public <S> boolean handleError(NativeWebRequest request, DeferredResult<S> deferredResult, Throwable t) { 312 try { 313 if (errorCallback != null) { 314 errorCallback.accept(t); 315 } 316 } 317 finally { 318 try { 319 setResultInternal(t); 320 } 321 catch (Throwable ex) { 322 logger.debug("Failed to handle error result", ex); 323 } 324 } 325 return false; 326 } 327 @Override 328 public <S> void afterCompletion(NativeWebRequest request, DeferredResult<S> deferredResult) { 329 expired = true; 330 if (completionCallback != null) { 331 completionCallback.run(); 332 } 333 } 334 }; 335 } 336 337 338 /** 339 * Handles a DeferredResult value when set. 340 */ 341 @FunctionalInterface 342 public interface DeferredResultHandler { 343 344 void handleResult(Object result); 345 } 346 347 }
可以看到拦截器内部判断,如果不是默认的空值会自己内部setResult 然后进行处理。
2. 原理简单理解
1. 关于spring 对返回结果是 DeferredResult 的拦截以及处理
断点下在org.springframework.web.context.request.async.DeferredResult#setResultHandler 方法,查看调用链:
可以看到核心也是走SpringMVC的任务分发。 然后对搜集到的结果进行处理。几个关键逻辑如下:
1》org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); Object result; try { WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod); ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = this.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 asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(this.logger, (traceOn) -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); if (!asyncManager.isConcurrentHandlingStarted()) { ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest); return var15; } result = null; } finally { webRequest.requestCompleted(); } return (ModelAndView)result; }
可以看到这里有就是获取ModelAndView 对象。
2》 invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); 会调用到 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs); this.setResponseStatus(webRequest); if (returnValue == null) { if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) { this.disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception var6) { if (this.logger.isTraceEnabled()) { this.logger.trace(this.formatErrorForReturnValue(returnValue), var6); } throw var6; } }
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#returnValueHandlers 对于响应结果的处理有如下处理器:
3》继续调用到:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } else { handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } }
实际就是根据返回类型选择合适的处理器,然后调用 handleReturnValue 方法进行后置处理。这里调用到:org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.web.servlet.mvc.method.annotation; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import org.springframework.core.MethodParameter; import org.springframework.lang.Nullable; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler { public DeferredResultMethodReturnValueHandler() { } public boolean supportsReturnType(MethodParameter returnType) { Class<?> type = returnType.getParameterType(); return DeferredResult.class.isAssignableFrom(type) || ListenableFuture.class.isAssignableFrom(type) || CompletionStage.class.isAssignableFrom(type); } public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { mavContainer.setRequestHandled(true); } else { DeferredResult result; if (returnValue instanceof DeferredResult) { result = (DeferredResult)returnValue; } else if (returnValue instanceof ListenableFuture) { result = this.adaptListenableFuture((ListenableFuture)returnValue); } else { if (!(returnValue instanceof CompletionStage)) { throw new IllegalStateException("Unexpected return value type: " + returnValue); } result = this.adaptCompletionStage((CompletionStage)returnValue); } WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, new Object[]{mavContainer}); } } private DeferredResult<Object> adaptListenableFuture(ListenableFuture<?> future) { final DeferredResult<Object> result = new DeferredResult(); future.addCallback(new ListenableFutureCallback<Object>() { public void onSuccess(@Nullable Object value) { result.setResult(value); } public void onFailure(Throwable ex) { result.setErrorResult(ex); } }); return result; } private DeferredResult<Object> adaptCompletionStage(CompletionStage<?> future) { DeferredResult<Object> result = new DeferredResult(); future.handle((value, ex) -> { if (ex != null) { if (ex instanceof CompletionException && ex.getCause() != null) { ex = ex.getCause(); } result.setErrorResult(ex); } else { result.setResult(value); } return null; }); return result; } }
4》实际最终都会交给org.springframework.web.context.request.async.WebAsyncManager 类进行处理:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.web.context.request.async; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; public final class WebAsyncManager { private static final Object RESULT_NONE = new Object(); private static final AsyncTaskExecutor DEFAULT_TASK_EXECUTOR = new SimpleAsyncTaskExecutor(WebAsyncManager.class.getSimpleName()); private static final Log logger = LogFactory.getLog(WebAsyncManager.class); private static final CallableProcessingInterceptor timeoutCallableInterceptor = new TimeoutCallableProcessingInterceptor(); private static final DeferredResultProcessingInterceptor timeoutDeferredResultInterceptor = new TimeoutDeferredResultProcessingInterceptor(); private static Boolean taskExecutorWarning = true; private AsyncWebRequest asyncWebRequest; private AsyncTaskExecutor taskExecutor; private volatile Object concurrentResult; private volatile Object[] concurrentResultContext; private final Map<Object, CallableProcessingInterceptor> callableInterceptors; private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors; WebAsyncManager() { this.taskExecutor = DEFAULT_TASK_EXECUTOR; this.concurrentResult = RESULT_NONE; this.callableInterceptors = new LinkedHashMap(); this.deferredResultInterceptors = new LinkedHashMap(); } public void setAsyncWebRequest(AsyncWebRequest asyncWebRequest) { Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null"); this.asyncWebRequest = asyncWebRequest; this.asyncWebRequest.addCompletionHandler(() -> { asyncWebRequest.removeAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, 0); }); } public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } public boolean isConcurrentHandlingStarted() { return this.asyncWebRequest != null && this.asyncWebRequest.isAsyncStarted(); } public boolean hasConcurrentResult() { return this.concurrentResult != RESULT_NONE; } public Object getConcurrentResult() { return this.concurrentResult; } public Object[] getConcurrentResultContext() { return this.concurrentResultContext; } @Nullable public CallableProcessingInterceptor getCallableInterceptor(Object key) { return (CallableProcessingInterceptor)this.callableInterceptors.get(key); } @Nullable public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) { return (DeferredResultProcessingInterceptor)this.deferredResultInterceptors.get(key); } public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) { Assert.notNull(key, "Key is required"); Assert.notNull(interceptor, "CallableProcessingInterceptor is required"); this.callableInterceptors.put(key, interceptor); } public void registerCallableInterceptors(CallableProcessingInterceptor... interceptors) { Assert.notNull(interceptors, "A CallableProcessingInterceptor is required"); CallableProcessingInterceptor[] var2 = interceptors; int var3 = interceptors.length; for(int var4 = 0; var4 < var3; ++var4) { CallableProcessingInterceptor interceptor = var2[var4]; String key = interceptor.getClass().getName() + ":" + interceptor.hashCode(); this.callableInterceptors.put(key, interceptor); } } public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) { Assert.notNull(key, "Key is required"); Assert.notNull(interceptor, "DeferredResultProcessingInterceptor is required"); this.deferredResultInterceptors.put(key, interceptor); } public void registerDeferredResultInterceptors(DeferredResultProcessingInterceptor... interceptors) { Assert.notNull(interceptors, "A DeferredResultProcessingInterceptor is required"); DeferredResultProcessingInterceptor[] var2 = interceptors; int var3 = interceptors.length; for(int var4 = 0; var4 < var3; ++var4) { DeferredResultProcessingInterceptor interceptor = var2[var4]; String key = interceptor.getClass().getName() + ":" + interceptor.hashCode(); this.deferredResultInterceptors.put(key, interceptor); } } public void clearConcurrentResult() { synchronized(this) { this.concurrentResult = RESULT_NONE; this.concurrentResultContext = null; } } public void startCallableProcessing(Callable<?> callable, Object... processingContext) throws Exception { Assert.notNull(callable, "Callable must not be null"); this.startCallableProcessing(new WebAsyncTask(callable), processingContext); } public void startCallableProcessing(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; } else { this.logExecutorWarning(); } List<CallableProcessingInterceptor> interceptors = new ArrayList(); interceptors.add(webAsyncTask.getInterceptor()); interceptors.addAll(this.callableInterceptors.values()); interceptors.add(timeoutCallableInterceptor); Callable<?> callable = webAsyncTask.getCallable(); CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(() -> { if (logger.isDebugEnabled()) { logger.debug("Async request timeout for " + this.formatRequestUri()); } Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable); if (result != CallableProcessingInterceptor.RESULT_NONE) { this.setConcurrentResultAndDispatch(result); } }); this.asyncWebRequest.addErrorHandler((ex) -> { if (logger.isDebugEnabled()) { logger.debug("Async request error for " + this.formatRequestUri() + ": " + ex); } Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex); result = result != CallableProcessingInterceptor.RESULT_NONE ? result : ex; this.setConcurrentResultAndDispatch(result); }); this.asyncWebRequest.addCompletionHandler(() -> { interceptorChain.triggerAfterCompletion(this.asyncWebRequest, callable); }); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable); this.startAsyncProcessing(processingContext); try { Future<?> future = this.taskExecutor.submit(() -> { Object result = null; try { interceptorChain.applyPreProcess(this.asyncWebRequest, callable); result = callable.call(); } catch (Throwable var8) { result = var8; } finally { result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, result); } this.setConcurrentResultAndDispatch(result); }); interceptorChain.setTaskFuture(future); } catch (RejectedExecutionException var10) { Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, var10); this.setConcurrentResultAndDispatch(result); throw var10; } } private void logExecutorWarning() { if (taskExecutorWarning && logger.isWarnEnabled()) { synchronized(DEFAULT_TASK_EXECUTOR) { AsyncTaskExecutor executor = this.taskExecutor; if (taskExecutorWarning && (executor instanceof SimpleAsyncTaskExecutor || executor instanceof SyncTaskExecutor)) { String executorTypeName = executor.getClass().getSimpleName(); logger.warn("\n!!!\nAn Executor is required to handle java.util.concurrent.Callable return values.\nPlease, configure a TaskExecutor in the MVC config under \"async support\".\nThe " + executorTypeName + " currently in use is not suitable under load.\n-------------------------------\nRequest URI: '" + this.formatRequestUri() + "'\n!!!"); taskExecutorWarning = false; } } } } private String formatRequestUri() { HttpServletRequest request = (HttpServletRequest)this.asyncWebRequest.getNativeRequest(HttpServletRequest.class); return request != null ? request.getRequestURI() : "servlet container"; } private void setConcurrentResultAndDispatch(Object result) { synchronized(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: " + this.formatRequestUri()); } } else { if (logger.isDebugEnabled()) { boolean isError = result instanceof Throwable; logger.debug("Async " + (isError ? "error" : "result set") + ", dispatch to " + this.formatRequestUri()); } this.asyncWebRequest.dispatch(); } } public void startDeferredResultProcessing(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); DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(() -> { try { interceptorChain.triggerAfterTimeout(this.asyncWebRequest, deferredResult); } catch (Throwable var4) { this.setConcurrentResultAndDispatch(var4); } }); this.asyncWebRequest.addErrorHandler((ex) -> { try { if (!interceptorChain.triggerAfterError(this.asyncWebRequest, deferredResult, ex)) { return; } deferredResult.setErrorResult(ex); } catch (Throwable var5) { this.setConcurrentResultAndDispatch(var5); } }); this.asyncWebRequest.addCompletionHandler(() -> { interceptorChain.triggerAfterCompletion(this.asyncWebRequest, deferredResult); }); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult); this.startAsyncProcessing(processingContext); try { interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult); deferredResult.setResultHandler((result) -> { result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result); this.setConcurrentResultAndDispatch(result); }); } catch (Throwable var7) { this.setConcurrentResultAndDispatch(var7); } } private void startAsyncProcessing(Object[] processingContext) { synchronized(this) { this.concurrentResult = RESULT_NONE; this.concurrentResultContext = processingContext; } this.asyncWebRequest.startAsync(); if (logger.isDebugEnabled()) { logger.debug("Started async request"); } } }
5》再往下追代码就是使用一些多线程机制加Spring 的事务机制进行封装,调度,然后进行处理。
2. 休眠时间长点,多次调用查看其内部线程状态
可以看到其实 getDeferredResultController 方法处理的比较快,用tomcat 的最小10个空余工作线程是完全够用的。因为该方法内部没有任何的逻辑。也就是说该种异步模式不会增大tomcat 线程数量。
3. SpringMVC 对于ResponseBody 响应的处理
简单研究下对于ResponseBody 的处理。
1. SpringMVC核心逻辑解读:
(1). org.springframework.web.servlet.DispatcherServlet#doDispatch SpringMVC 核心逻辑
1》一系列判断,然后获取handler
2》反射调用方法,然后获取modelAndView 对象。 如果获取的ModelAndView 对象不为空,就进行界面渲染之后返回前端
(2). org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal 到这里反射执行方法,然后返回ModelAndView 对象。 核心也就是在这里,如果是自己已经处理完结果,那么返回的modelAndView 为null 即可。
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { this.checkRequest(request); ModelAndView mav; if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized(mutex) { mav = this.invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this.invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this.invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader("Cache-Control")) { if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { this.prepareResponse(response); } } return mav; }
(3). 继续调用到:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndView var15; try { WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod); ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = this.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 asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); Object result; if (asyncManager.hasConcurrentResult()) { result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(this.logger, (traceOn) -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); if (asyncManager.isConcurrentHandlingStarted()) { result = null; return (ModelAndView)result; } var15 = this.getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } return var15; }
1》org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle 返回调用方法,执行对返回结果的处理
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs); this.setResponseStatus(webRequest); if (returnValue == null) { if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) { this.disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception var6) { if (this.logger.isTraceEnabled()) { this.logger.trace(this.formatErrorForReturnValue(returnValue), var6); } throw var6; } }
可以看到:反射调用获取到returnValue;然后org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue 从内置的一些返回结果处理器中获取handler 对结果进行处理
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
2》org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView 获取MAV对象
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } else { ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (!mavContainer.isViewReference()) { mav.setView((View)mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes(); HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; } }
可以看到这里有个核心的逻辑,如果 mavContainer.isRequestHandled() 请求处理完,那么返回为空。 也就是如果我们自己处理完,将这个参数设为True 即可。
2. org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 对结果处理
1》org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue 获取到RequestResponseBodyMethodProcessor
2》调用 RequestResponseBodyMethodProcessor.handleReturnValue方法处理结果
RequestResponseBodyMethodProcessor 相关方法如下:
public boolean supportsReturnType(MethodParameter returnType) { return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class); } public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest); ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest); this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
可以看到处理过程就是调用 setRequestHandled 设为true, 然后获取到request、response 然后写回结果。
4. 扩展自己的返回结果处理器
参考接口以及实现类:
org.springframework.web.method.support.HandlerMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
1. 编写处理类
package cn.qz.template.config; import cn.qz.common.utils.TypedResult; import org.springframework.core.MethodParameter; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.filter.ShallowEtagHeaderFilter; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletResponse; public class CustomReturnHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter methodParameter) { return methodParameter.getMethod().getReturnType().equals(TypedResult.class); } @Override public void handleReturnValue(Object returnValue, MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { mavContainer.setRequestHandled(true); HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(HttpServletResponse.class); Assert.state(response != null, "No HttpServletResponse"); ServerHttpResponse outputMessage = new ServletServerHttpResponse(response); ServletRequest request = (ServletRequest) webRequest.getNativeRequest(ServletRequest.class); Assert.state(request != null, "No ServletRequest"); ShallowEtagHeaderFilter.disableContentCaching(request); // 简单处理,只拿出其Msg 属性,然后返回到前端 response.getWriter().write(((TypedResult) returnValue).getMsg()); response.getWriter().flush(); } }
2. 注册到Spring
package cn.qz.template.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class MyWebConfiguration implements WebMvcConfigurer { @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) { handlers.add(new CustomReturnHandler()); } }
3. 添加测试类
package cn.qz.template.controller; import cn.qz.common.utils.TypedResult; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("/test2") @Controller public class TestController2 { @GetMapping public TypedResult test() { TypedResult<Object> objectTypedResult = new TypedResult<>(); objectTypedResult.setMsg("success"); return objectTypedResult; } }
4. 测试
qiao-zhi@qiao-zhideMBP ~ % curl http://localhost:8090/test2 success% qiao-zhi@qiao-zhideMBP ~ % curl http://localhost:8090/test2 success% qiao-zhi@qiao-zhideMBP ~ %