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 }
View Code

可以看到拦截器内部判断,如果不是默认的空值会自己内部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;
    }
}
View Code

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");
        }

    }
}
View Code

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;
    }
View Code

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 ~ %

 

posted @ 2023-01-08 17:17  QiaoZhi  阅读(860)  评论(0编辑  收藏  举报