17

I am using a Spring MVC controller and want to start the execution of a task in a new thread. However the task should not start immediately but only after the response has been sent to the client.

The sequence - in strict temporal order:

  1. request
  2. return new ResponseEntity ... / client receives HTTP status 200 OK.
  3. processing of the task begins.

How do I achieve this?

I wanted to use Spring's async abstraction by calling a method annotated with @Async, but it does not guarantee that the new thread waits for the response to be sent first.

NaanProphet
  • 340
  • 1
  • 3
  • 7
mreiterer
  • 556
  • 1
  • 4
  • 15

4 Answers4

17

You can use an interceptor for that. The order of events for handling a request in Spring MVC is:

  • DispatcherServlet get a Request, Response pair and determines the handling
  • [optional] interceptors preHandle are called (with option to stop processing)
  • controller is called
  • [optional] interceptors postHandle are called
  • ViewResolver and view do the actual Response processing and send the response
  • [optional] interceptors afterCompletion are called

The above is over simplified and is just aimed at showing that interceptor afterCompletion methods are called after the response has been sent to client, with following signature :

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

In that method, you can test the occurence of an exception and the correctness of the response (ex == null && response.getStatus() == HttpServletResponse.SC_OK) before starting your processing.

NaanProphet
  • 340
  • 1
  • 3
  • 7
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
6

The HandlerInterceptor is the solution, but the code get a little bit more complex than expected.

Here's a code suggestion to make it simpler by putting the whole solution in a single class:

private static final ThreadLocal<Object> result = new ThreadLocal<Object>();

@RequestMapping("/mypath")
public Object execute() throws Exception {
    Object obj = new Object();
    result.set(obj); // Save the object to be used after response
    return obj;
}

@Bean
public MappedInterceptor interceptor() {
    return new MappedInterceptor(Arrays.array("/mypath"), new HandlerInterceptor() {
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // Get the saved object
            Object results = result.get();

            // Clean for the next request
            result.set(null);

            // TODO Your code to be executed after response.
        }
    });
}
Italo Borssatto
  • 15,044
  • 7
  • 62
  • 88
5

If your "after respond is sent" requirement is fulfilled with "after the view has been rendered" you may use an implementation of HandlerInterceptor. For an example cf. Spring 3 MVC Interceptor tutorial with example, triggering your job in afterCompletion.

If your job needs to be triggered "after it hit the wire", I'd like to know why.

Hille
  • 4,096
  • 2
  • 22
  • 32
0

You could add the task to a blocking queue, before the response entity is created. Let a task executor run periodically (every x seconds) over the queue and poll for tasks. If a task is found, it will be executed. If not, the thread finishes its run method and waits for the next run (in x seconds).

How to run a task periodically: http://www.mkyong.com/java/how-to-run-a-task-periodically-in-java/

Inject the queue as dependency in both the controller and the task executor service. This should be an easy solution to start with.

In this szenario you can't be sure, that the client receives the request. But if you want to be safe(r), add a due date to your task object with a sufficient offset (e.g. current time + 30 seconds). Let the task executor check if the due date of the polled task is now or in the past. Otherwise ignore the task for this run.

gclaussn
  • 1,736
  • 16
  • 19