简体   繁体   中英

Spring Controller start processing after response is sent

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.

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.

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.

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

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 (eg 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM