Spring MVC - Intercepting Async process lifecycle using DeferredResultProcessingInterceptor

[Updated: Dec 14, 2016, Created: Dec 14, 2016]

We saw in the last tutorial, how to use CallableProcessingInterceptor to integrate more deeply with async processing lifecycle.

To have similar lifecycle interception around user defined 'DeferredResult' processing, we can register DeferredResultProcessingInterceptor



Example


Creating the DeferredResultProcessingInterceptor

package com.logicbig.example;

import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;

public class MyDeferredResultProcessingInterceptor implements
                               DeferredResultProcessingInterceptor {


    @Override
    public <T> void beforeConcurrentHandling (NativeWebRequest request,
                                              DeferredResult<T> deferredResult)
              throws Exception {
        System.out.println("deferredInterceptor#beforeConcurrentHandler called." +
                                     "Thread: " + Thread.currentThread().getName());

    }

    @Override
    public <T> void preProcess (NativeWebRequest request,
                                DeferredResult<T> deferredResult) throws Exception {
        System.out.println("deferredInterceptor#preProcess called." +
                                     "Thread: " + Thread.currentThread().getName());

    }

    @Override
    public <T> void postProcess (NativeWebRequest request,
                                 DeferredResult<T> deferredResult,
                                 Object concurrentResult) throws Exception {
        System.out.println("deferredInterceptor#postProcess called." +
                                     "Thread: " + Thread.currentThread().getName());

    }

    @Override
    public <T> boolean handleTimeout (NativeWebRequest request,
                                      DeferredResult<T> deferredResult)
                                                               throws Exception {
        System.out.println("deferredInterceptor#handleTimeout called." +
                                     "Thread: " + Thread.currentThread().getName());

        return false;
    }

    @Override
    public <T> void afterCompletion (NativeWebRequest request,
                                     DeferredResult<T> deferredResult)
                                                                 throws Exception {
        System.out.println("deferredInterceptor#afterCompletion called." +
                                     "Thread: " + Thread.currentThread().getName());

    }
}


Registering the interceptor

Similar to our last tutorial example, We can register the above interceptor in the HandlerInterceptor#preHandle method using WebAsyncManager:

package com.logicbig.example;

import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class MyAsyncHandlerInterceptor extends HandlerInterceptorAdapter {
    private static final Object DEFERRED_INTERCEPTOR_KEY = new Object();

    @Override
    public boolean preHandle (HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler) throws Exception {

        System.out.println("interceptor#preHandle called." +
                                     " Thread: " + Thread.currentThread()
                                                         .getName());

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerDeferredResultInterceptor(DEFERRED_INTERCEPTOR_KEY,
                                      new MyDeferredResultProcessingInterceptor());


        return true;

    }

   .......
}

The Controller:

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

@Controller
public class MyWebController {
    @RequestMapping("/")
    @ResponseBody
    public DeferredResult<String> handleTestRequest () {

        System.out.println("controller#handler called. Thread: " +
                                     Thread.currentThread()
                                           .getName());

        final DeferredResult<String> deferredResult = new DeferredResult<>();

        new Thread(() -> {
            System.out.println("controller-deferred#async task started. Thread: " +
                                         Thread.currentThread()
                                               .getName());
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            deferredResult.setResult("test async result");
            System.out.println("controller-deferred#async task finished");
        }).start();


        System.out.println("controller#handler finished");
        return deferredResult;
    }
}

The main class is same as of our last example. On running the main class and accessing the application in the browser. We will have following output:

interceptor#preHandle called. Thread: http-nio-8080-exec-1
controller#handler called. Thread: http-nio-8080-exec-1
controller#handler finished
controller-deferred#async task started. Thread: Thread-3
deferredInterceptor#beforeConcurrentHandler called.Thread: http-nio-8080-exec-1
deferredInterceptor#preProcess called.Thread: http-nio-8080-exec-1
interceptor#afterConcurrentHandlingStarted called. Thread: http-nio-8080-exec-1
deferredInterceptor#postProcess called.Thread: Thread-3
controller-deferred#async task finished
interceptor#preHandle called. Thread: http-nio-8080-exec-2
interceptor#postHandle called.Thread: http-nio-8080-exec-2
interceptor#afterCompletion called Thread.: http-nio-8080-exec-2
deferredInterceptor#afterCompletion called.Thread: http-nio-8080-exec-2

This output is similar to the last example. In this case, the user is responsible to create his own thread for async processing in the controller method. User would normally call DeferredResult.setResult() at the end of the processing, which will cause DeferredInterceptor#postProcess invocation. To understand the all interception points, here's the flow diagram:




Example Project

Dependencies and Technologies Used :

  • Spring Boot Web Starter 1.4.2.RELEASE: Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container.
    Corresponding Spring version: 4.3.4.RELEASE
  • JDK 1.8
  • Maven 3.3.9

Async Deferred Processing Select All Download
  • async-deferred-process-interceptor
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also