Add interceptors for async processing

This change introduces two new interceptors with callback methods
for concurrent request handling. These interfaces are
CallableProcessingInterceptor and DeferredResultProcessingInterceptor.

Unlike a HandlerInterceptor, and its AsyncHandlerInterceptor sub-type,
which intercepts the invocation of a handler in he main request
processing thread, the two new interfaces are aimed at intercepting the
asynchronous execution of a Callable or a DeferredResult.

This allows for the registration of thread initialization logic in the
case of Callable executed with an AsyncTaskExecutor, or for centralized
tracking of the completion and/or expiration of a DeferredResult.
This commit is contained in:
Rossen Stoyanchev
2012-09-19 09:25:50 -04:00
parent 30bce7fa16
commit 57c36dd395
24 changed files with 735 additions and 267 deletions

View File

@@ -19,18 +19,19 @@ package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
/**
* Extends {@code HandlerInterceptor} with a callback method invoked during
* asynchronous request handling.
*
* <p>When a handler starts asynchronous request handling, the DispatcherServlet
* exits without invoking {@code postHandle} and {@code afterCompletion}, as it
* normally does, since the results of request handling (e.g. ModelAndView) are
* not available in the current thread and handling is not yet complete.
* In such scenarios, the
* normally does, since the results of request handling (e.g. ModelAndView)
* will. be produced concurrently in another thread. In such scenarios,
* {@link #afterConcurrentHandlingStarted(HttpServletRequest, HttpServletResponse)}
* method is invoked instead allowing implementations to perform tasks such as
* cleaning up thread bound attributes.
* is invoked instead allowing implementations to perform tasks such as cleaning
* up thread bound attributes.
*
* <p>When asynchronous handling completes, the request is dispatched to the
* container for further processing. At this stage the DispatcherServlet invokes
@@ -40,20 +41,26 @@ import javax.servlet.http.HttpServletResponse;
* @since 3.2
*
* @see org.springframework.web.context.request.async.WebAsyncManager
* @see org.springframework.web.context.request.async.CallableProcessingInterceptor
* @see org.springframework.web.context.request.async.DeferredResultProcessingInterceptor
*/
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
/**
* Called instead of {@code postHandle} and {@code afterCompletion}, when the
* a handler is being executed concurrently. Implementations may use the provided
* request and response but should avoid modifying them in ways that would
* conflict with the concurrent execution of the handler. A typical use of
* this method would be to clean thread local variables.
* Called instead of {@code postHandle} and {@code afterCompletion}, when
* the a handler is being executed concurrently. Implementations may use the
* provided request and response but should avoid modifying them in ways
* that would conflict with the concurrent execution of the handler. A
* typical use of this method would be to clean thread local variables.
*
* @param request the current request
* @param response the current response
* @param handler handler that started async execution, for type and/or instance examination
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @throws Exception in case of errors
*/
void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler);
void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
}

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -44,12 +45,13 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncManager.WebAsyncThreadInitializer;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.ServletRequestHandledEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
@@ -909,7 +911,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
initContextHolders(request, localeContext, requestAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerAsyncThreadInitializer(this.getClass().getName(), createAsyncThreadInitializer(request));
asyncManager.registerCallableInterceptor(this.getClass().getName(), createRequestBindingInterceptor(request));
try {
doService(request, response);
@@ -992,13 +994,15 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
}
private WebAsyncThreadInitializer createAsyncThreadInitializer(final HttpServletRequest request) {
private CallableProcessingInterceptor createRequestBindingInterceptor(final HttpServletRequest request) {
return new WebAsyncThreadInitializer() {
public void initialize() {
return new CallableProcessingInterceptor() {
public void preProcess(NativeWebRequest webRequest, Callable<?> task) {
initContextHolders(request, buildLocaleContext(request), new ServletRequestAttributes(request));
}
public void reset() {
public void postProcess(NativeWebRequest webRequest, Callable<?> task, Object concurrentResult) {
resetContextHolders(request, null, null);
}
};

View File

@@ -19,6 +19,8 @@ package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
/**
* Workflow interface that allows for customized handler execution chains.
* Applications can register any number of existing or custom interceptors
@@ -36,8 +38,8 @@ import javax.servlet.http.HttpServletResponse;
* {@code postHandle} and {@code afterCompletion} callbacks. When concurrent
* handler execution completes, the request is dispatched back in order to
* proceed with rendering the model and all methods of this contract are invoked
* again. For further options and comments see
* {@code org.springframework.web.servlet.HandlerInterceptor}
* again. For further options and details see
* {@code org.springframework.web.servlet.AsyncHandlerInterceptor}
*
* <p>Typically an interceptor chain is defined per HandlerMapping bean,
* sharing its granularity. To be able to apply a certain interceptor chain
@@ -100,7 +102,8 @@ public interface HandlerInterceptor {
* getting applied in inverse order of the execution chain.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @param modelAndView the <code>ModelAndView</code> that the handler returned
* (can also be <code>null</code>)
* @throws Exception in case of errors
@@ -120,7 +123,8 @@ public interface HandlerInterceptor {
* the last to be invoked.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance examination
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2006 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ package org.springframework.web.servlet.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
@@ -29,7 +29,7 @@ import org.springframework.web.servlet.ModelAndView;
* @author Juergen Hoeller
* @since 05.12.2003
*/
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
/**
* This implementation always returns <code>true</code>.
@@ -55,4 +55,12 @@ public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
throws Exception {
}
/**
* This implementation is empty.
*/
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
}
}