1.DeferredResultThe sample

1.1 Creating Instance Objects

DeferredResult<ResponseEntity<List<User>>> deferredResult =

                new DeferredResult<>(20000L.new ResponseEntity<>(HttpStatus.NOT_MODIFIED));

Copy the code

1.2 Setting the callback

deferredResult.onTimeout(() -> {

    log.info("Call timeout");

});



deferredResult.onCompletion(() -> {

    log.info("Call completed");

});

Copy the code

1.3 Setting Results

new Thread(() -> {

    try {

        TimeUnit.SECONDS.sleep(10);

        deferredResult.setResult(new ResponseEntity<>(userService.listUser(), HttpStatus.OK));

    } catch (InterruptedException e) {

        e.printStackTrace();

    }

}).start();

Copy the code

1.4 Complete Example

@GetMapping("/deferredResultUser")

public DeferredResult<ResponseEntity<List<User>>> deferredResultListUser() {

    DeferredResult<ResponseEntity<List<User>>> deferredResult =

        new DeferredResult<>(20000L.new ResponseEntity<>(HttpStatus.NOT_MODIFIED));

    deferredResult.onTimeout(() -> {

        log.info("Call timeout");

    });



    deferredResult.onCompletion(() -> {

        log.info("Call completed");

    });



    new Thread(() -> {

        try {

            TimeUnit.SECONDS.sleep(10);

            deferredResult.setResult(new ResponseEntity<>(userService.listUser(), HttpStatus.OK));

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }).start();

    return deferredResult;

}

Copy the code

When a client request is mapped to a controller method that returns a value of DeferredResult, the Tomcat thread is immediately released and the request is suspended until the setResult() method is called or timed out.

2.DeferredResultThe principle of analysis

2.1 Return Value The handler processes the return value

Controller method return values are made by the corresponding processor for processing, about the DeferredResult, natural by DeferredResultMethodReturnValueHandler for processing.

@Override

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,

                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
 throws Exception 
{



DeferredResult<? > result;



    // The return type is DeferredResult

    if (returnValue instanceof DeferredResult) {

result = (DeferredResult<? >) returnValue;

    }

    

    // Return a value of type ListenableFuture for adaptation conversion

    else if (returnValue instanceof ListenableFuture) {

result = adaptListenableFuture((ListenableFuture<? >) returnValue);

    } 

    

    // The return value type is CompletionStage for adaptation conversion

    else if (returnValue instanceof CompletionStage) {

result = adaptCompletionStage((CompletionStage<? >) returnValue);

    } else {

        // Should not happen...

        throw new IllegalStateException("Unexpected return value type: " + returnValue);

    }



    / / processing DeferredResult

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

}

Copy the code

2.2 set upDeferredResultHandler

public void startDeferredResultProcessing(

   finalDeferredResult<? > deferredResult, Object... processingContext)
 throws Exception 
{

    // 1. Enable asynchronous processing

    startAsyncProcessing(processingContext);



    try {

        // set DeferredResultHandler

        deferredResult.setResultHandler(result -> {

            result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);

            setConcurrentResultAndDispatch(result);

        });

    }

    catch (Throwable ex) {

        setConcurrentResultAndDispatch(ex);

    }

}

Copy the code

2.3 callsetResult()

When our asynchronous task completes, the setResult() method of DeferredResult is called

public boolean setResult(T result) {

    return setResultInternal(result);

}

Copy the code
private boolean setResultInternal(Object result) {

    DeferredResultHandler resultHandlerToUse;

    synchronized (this) {

        // At this point, we got a new result to process

        this.result = result;

        resultHandlerToUse = this.resultHandler;

        if (resultHandlerToUse == null) {

            return true;

        }

        this.resultHandler = null;

    }

    // Call the handleResult() method of DeferredResultHandler

    resultHandlerToUse.handleResult(result);

    return true;

}

Copy the code

In chapter 2.2 set up DeferredResultHandler, so will call setConcurrentResultAndDispatch ()

private void setConcurrentResultAndDispatch(Object result) {

    synchronized (WebAsyncManager.this) {

        if (this.concurrentResult ! = RESULT_NONE) {

            return;

        }

        // 1. Set the result

        this.concurrentResult = result;

        this.errorHandlingInProgress = (result instanceof Throwable);

    }



    if (this.asyncWebRequest.isAsyncComplete()) {

        if (logger.isDebugEnabled()) {

            logger.debug("Async result set but request already complete: " + formatRequestUri());

        }

        return;

    }



    if (logger.isDebugEnabled()) {

        boolean isError = result instanceof Throwable;

        logger.debug("Async " + (isError ? "error" : "result set") + ", dispatch to " + formatRequestUri());

    }

    // 2. Request scheduling is to simulate the client to initiate a request to the server again

    this.asyncWebRequest.dispatch();

}

Copy the code

2.4 Scheduling and Processing

@Nullable

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,

                                           HttpServletResponse response, HandlerMethod handlerMethod)
 throws Exception 
{



    ServletWebRequest webRequest = new ServletWebRequest(request, response);

    try {

        // The result is set in Section 2.3, so this logic returns true

        if (asyncManager.hasConcurrentResult()) {

            Object result = asyncManager.getConcurrentResult();

            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];

            asyncManager.clearConcurrentResult();

            LogFormatUtils.traceDebug(logger, traceOn -> {

String formatted = LogFormatUtils.formatValue(result, ! traceOn);

                return "Resume with async result [" + formatted + "]";

            });

            invocableMethod = invocableMethod.wrapConcurrentResult(result);

        }



        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        if (asyncManager.isConcurrentHandlingStarted()) {

            return null;

        }



        return getModelAndView(mavContainer, modelFactory, webRequest);

    }

    finally {

        webRequest.requestCompleted();

    }

}

Copy the code

2.5 buildServletInvocableHandlerMethod

ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {

    return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));

}

Copy the code
public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {

    // Create a Callable object and specify that the call() method is called

    super((Callable<Object>) () -> {

        if (result instanceof Exception) {

            throw (Exception) result;

        }

        else if (result instanceof Throwable) {

            throw new NestedServletException("Async processing failed", (Throwable) result);

        }

        return result;

    }, CALLABLE_METHOD);



    if (ServletInvocableHandlerMethod.this.returnValueHandlers ! =null) {

        setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);

    }

    this.returnType = returnType;

}

Copy the code

The above logic creates the Callable object and specifies that the call() method be called

2.6 Calling the target method

Call Callable’s Call () method, get the return result, and use the return value handler to process the return value

3. Summary

  • The method defined in the controller returns a value of DeferredResult, which immediately releases the Tomcat thread and uses the business thread to process the service

  • Handled by DeferredResultMethodReturnValueHandler returns as a result, the open DeferredResultHandler asynchronous processing and Settings

  • Call the setResult() method when the business is done, followed by a callback to handleResult() for DeferredResultHandler

  • Sets the result and schedules the request

  • Create a Callable object and set the call method to Call ()

  • Call () is called reflectively to get the return value

  • The return value is processed using a return value handler