Spring MVC - Intercepting Async Requests using AsyncHandlerInterceptor

[Updated: Dec 12, 2016, Created: Dec 9, 2016]

Spring extends the concept of intercepting requests when it comes to asynchronous processing.

Instead of using HandlerInterceptor, the interface AsyncHandlerInterceptor implementations can be used to intercept request involving async processing.

AsyncHandlerInterceptor defines one method

void afterConcurrentHandlingStarted(HttpServletRequest request,
                                    HttpServletResponse response,
                                    Object handler)
                             throws Exception

AsyncHandlerInterceptor is a sub-interface of HandlerInterceptor.

HandlerInterceptorAdapter implements AsyncHandlerInterceptor, that means we can use the same adapter for async processing.


How that works? let's understand that with an example.


Creating the interceptor

package com.logicbig.example;

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 {

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

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

    }

    @Override
    public void postHandle (HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler,
                        ModelAndView modelAndView) throws Exception {

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

    @Override
    public void afterCompletion (HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler, Exception ex) throws Exception {

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

    @Override
    public void afterConcurrentHandlingStarted (HttpServletRequest request,
                        HttpServletResponse response,
                        Object handler) throws Exception {

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


Creating 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 java.util.concurrent.Callable;

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

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

        Callable<String> callable = new Callable<String>() {
            @Override
            public String call () throws Exception {
                System.out.println("controller#async task started. Thread: " +
                                                       Thread.currentThread()
                                                             .getName());
                Thread.sleep(300);
                System.out.println("controller#async task finished");
                return "async result";
            }
        };

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


Creating Spring boot main class

From now on, we are going to use Spring boot for our MVC examples as it eliminates the need of creating a lot of boilerplate configurations. That way we can directly focus on the concept under discussion.

Please check out Spring boot tutorials.

Here's our main class. We are also registering our interceptor here. One role of the classes which are annotated with SpringBootApplication is that: they are @Configuration classes as well.

package com.logicbig.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

public class Main {
    public static void main (String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }

    @SpringBootApplication
    public static class BootApplication extends WebMvcConfigurerAdapter {
        @Override
        public void addInterceptors (InterceptorRegistry registry) {
            registry.addInterceptor(new MyAsyncHandlerInterceptor());
        }
    }
}

Running the application

Please run the main class which we created in the last part. That will also start the embedded tomcat server.

Access the application on the browser, we will have this output:


On console we have this output related to our request processing:

interceptor#preHandler called. Thread: http-nio-8080-exec-1
controller#handler called. Thread: http-nio-8080-exec-1
controller#handler finished
interceptor#afterConcurrentHandlingStarted called. Thread: http-nio-8080-exec-1
controller-callable#async task started. Thread: MvcAsync1
controller-callable#async task finished
interceptor#preHandler called. Thread: http-nio-8080-exec-2
interceptor#postHandler called. Thread: http-nio-8080-exec-2
interceptor#afterCompletion called Thread.: http-nio-8080-exec-2

The main request thread should not be blocked for a long time. It should exit immediately after starting a new thread in the handler method. The new thread will perform some application specific long running task.

The method AsyncHandlerInterceptor#afterConcurrentHandlingStarted is called after a new thread for async process (Callable) has been created. This interceptor call is made in the same request thread.

afterConcurrentHandlingStarted invocation is a chance for developer to perform tasks such as cleaning up thread-bound attributes before releasing the thread to the Servlet container.

Just after completing user defined async processing, new series of calls of preHandler(), postHandler() and afterCompletion() are made in a new thread but on the same instance of the interceptor. Since this interception happens during the final response, the developer can apply application logic like login etc as we saw in the HandlerInterceptor tutorial.

To understand what's going on, here is the flow diagram:


Note that the method afterConcurrentHandlingStarted might get called more or less same time when async processing starts in the new thread. This call might be before or after the async processing starts, that's due to thread scheduling differences, but it shouldn't effect our logic which we are supposed to apply in afterConcurrentHandlingStarted.

Within interceptor, to distinguish between the initial request and the subsequent dispatch after asynchronous handling completes, interceptors can check whether the javax.servlet.DispatcherType returns from ServletRequest#getDispatcherType() call is "REQUEST" or "ASYNC".

Also note that we used Callable approach for long running task in the above example. We can also use DeferredResult approach as well with the same interceptor. Please see examples here





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

Intercepting Async Processing Select All Download
  • async-handler-interceptor-example
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also