Add config options for MVC async interceptors

The MVC namespace and the MVC Java config now allow configuring
CallableProcessingInterceptor and DeferredResultProcessingInterceptor
instances.

Issue: SPR-9914
This commit is contained in:
Rossen Stoyanchev
2012-10-26 21:29:54 -04:00
parent e4a13cd4ad
commit e14ba9dec3
10 changed files with 240 additions and 16 deletions

View File

@@ -175,6 +175,11 @@ public final class WebAsyncManager {
this.callableInterceptors.put(key, interceptor);
}
public void registerAllCallableInterceptors(Map<Object, CallableProcessingInterceptor> interceptors) {
Assert.notNull(interceptors);
this.callableInterceptors.putAll(interceptors);
}
/**
* Register a {@link DeferredResultProcessingInterceptor} that will be
* applied when concurrent request handling with a {@link DeferredResult}
@@ -188,6 +193,11 @@ public final class WebAsyncManager {
this.deferredResultInterceptors.put(key, interceptor);
}
public void registerAllDeferredResultInterceptors(Map<Object, DeferredResultProcessingInterceptor> interceptors) {
Assert.notNull(interceptors);
this.deferredResultInterceptors.putAll(interceptors);
}
/**
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.

View File

@@ -174,6 +174,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
String asyncTimeout = getAsyncTimeout(element, source, parserContext);
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
@@ -197,6 +199,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
if (asyncExecutor != null) {
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
@@ -337,6 +341,40 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return null;
}
private ManagedList<?> getCallableInterceptors(Element element, Object source, ParserContext parserContext) {
ManagedList<? super Object> interceptors = new ManagedList<Object>();
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
if (asyncElement != null) {
Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "callable-interceptors");
if (interceptorsElement != null) {
interceptors.setSource(source);
for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
interceptors.add(beanDef);
}
}
}
return interceptors;
}
private ManagedList<?> getDeferredResultInterceptors(Element element, Object source, ParserContext parserContext) {
ManagedList<? super Object> interceptors = new ManagedList<Object>();
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
if (asyncElement != null) {
Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "deferred-result-interceptors");
if (interceptorsElement != null) {
interceptors.setSource(source);
for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
interceptors.add(beanDef);
}
}
}
return interceptors;
}
private ManagedList<?> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
if (resolversElement != null) {

View File

@@ -15,11 +15,18 @@
*/
package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
/**
* Helps with configuring a options for asynchronous request processing.
@@ -33,6 +40,13 @@ public class AsyncSupportConfigurer {
private Long timeout;
private final List<CallableProcessingInterceptor> callableInterceptors =
new ArrayList<CallableProcessingInterceptor>();
private final List<DeferredResultProcessingInterceptor> deferredResultInterceptors =
new ArrayList<DeferredResultProcessingInterceptor>();
/**
* Set the default {@link AsyncTaskExecutor} to use when a controller method
* returns a {@link Callable}. Controller methods can override this default on
@@ -64,6 +78,31 @@ public class AsyncSupportConfigurer {
return this;
}
/**
* Configure lifecycle intercepters with callbacks around concurrent request
* execution that starts when a controller returns a
* {@link java.util.concurrent.Callable}.
*
* @param interceptors the interceptors to register
*/
public AsyncSupportConfigurer registerCallableInterceptors(CallableProcessingInterceptor... interceptors) {
Assert.notNull(interceptors, "Interceptors are required");
this.callableInterceptors.addAll(Arrays.asList(interceptors));
return this;
}
/**
* Configure lifecycle intercepters with callbacks around concurrent request
* execution that starts when a controller returns a {@link DeferredResult}.
*
* @param interceptors the interceptors to register
*/
public AsyncSupportConfigurer registerDeferredResultInterceptors(DeferredResultProcessingInterceptor... interceptors) {
Assert.notNull(interceptors, "Interceptors are required");
this.deferredResultInterceptors.addAll(Arrays.asList(interceptors));
return this;
}
protected AsyncTaskExecutor getTaskExecutor() {
return this.taskExecutor;
}
@@ -72,4 +111,12 @@ public class AsyncSupportConfigurer {
return this.timeout;
}
protected List<CallableProcessingInterceptor> getCallableInterceptors() {
return this.callableInterceptors;
}
protected List<DeferredResultProcessingInterceptor> getDeferredResultInterceptors() {
return this.deferredResultInterceptors;
}
}

View File

@@ -378,6 +378,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}

View File

@@ -48,6 +48,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.accept.ContentNegotiationManager;
@@ -64,6 +65,8 @@ import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncTask;
import org.springframework.web.context.request.async.AsyncWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.ControllerAdviceBean;
@@ -135,6 +138,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
private Long asyncRequestTimeout;
private final Map<Object, CallableProcessingInterceptor> callableInterceptors =
new LinkedHashMap<Object, CallableProcessingInterceptor>();
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
private boolean ignoreDefaultModelOnRedirect = false;
private int cacheSecondsForSessionAttributeHandlers = 0;
@@ -363,6 +372,34 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
this.asyncRequestTimeout = timeout;
}
/**
* Configure {@code CallableProcessingInterceptor}'s to register on async requests.
* @param interceptors the interceptors to register
*/
public void setCallableInterceptors(List<CallableProcessingInterceptor> interceptors) {
Assert.notNull(interceptors);
for (int index = 0 ; index < interceptors.size(); index++) {
CallableProcessingInterceptor interceptor = interceptors.get(index);
this.callableInterceptors.put(getInterceptorKey(interceptor, index), interceptor);
}
}
/**
* Configure {@code DeferredResultProcessingInterceptor}'s to register on async requests.
* @param interceptors the interceptors to register
*/
public void setDeferredResultInterceptors(List<DeferredResultProcessingInterceptor> interceptors) {
Assert.notNull(interceptors);
for (int index = 0 ; index < interceptors.size(); index++) {
DeferredResultProcessingInterceptor interceptor = interceptors.get(index);
this.deferredResultInterceptors.put(getInterceptorKey(interceptor, index), interceptor);
}
}
private String getInterceptorKey(Object interceptor, int index) {
return this.hashCode() + ":" + interceptor.getClass().getName() + "#" + index;
}
/**
* By default the content of the "default" model is used both during
* rendering and redirect scenarios. Alternatively a controller method
@@ -703,6 +740,8 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerAllCallableInterceptors(this.callableInterceptors);
asyncManager.registerAllDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();

View File

@@ -97,6 +97,46 @@
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:all minOccurs="0">
<xsd:element name="callable-interceptors" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
The ordered set of interceptors that intercept the lifecycle of concurrently executed
requests, which start after a controller returns a java.util.concurrent.Callable.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Registers a CallableProcessingInterceptor.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="deferred-result-interceptors" minOccurs="0">
<xsd:annotation>
<xsd:documentation><![CDATA[
The ordered set of interceptors that intercept the lifecycle of concurrently executed
requests, which start after a controller returns a DeferredResult.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Registers a DeferredResultProcessingInterceptor.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:all>
<xsd:attribute name="task-executor" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.task.AsyncTaskExecutor"><![CDATA[

View File

@@ -16,6 +16,13 @@
package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
@@ -23,12 +30,13 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -57,6 +65,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod;
@@ -76,8 +88,6 @@ import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import static org.junit.Assert.*;
/**
* @author Keith Donald
* @author Arjen Poutsma
@@ -469,8 +479,18 @@ public class MvcNamespaceTests {
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass());
assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout"));
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
assertEquals(ConcurrentTaskExecutor.class, fieldAccessor.getPropertyValue("taskExecutor").getClass());
assertEquals(2500L, fieldAccessor.getPropertyValue("asyncRequestTimeout"));
Map<Object, CallableProcessingInterceptor> callableInterceptors =
(Map<Object, CallableProcessingInterceptor>) fieldAccessor.getPropertyValue("callableInterceptors");
assertEquals(1, callableInterceptors.size());
Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
(Map<Object, DeferredResultProcessingInterceptor>) fieldAccessor.getPropertyValue("deferredResultInterceptors");
assertEquals(1, deferredResultInterceptors.size());
}
@@ -539,4 +559,8 @@ public class MvcNamespaceTests {
}
}
public static class TestCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { }
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }
}

View File

@@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +47,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -117,6 +122,7 @@ public class WebMvcConfigurationSupportExtensionTests {
assertNotNull(handler.getHandler());
}
@SuppressWarnings("unchecked")
@Test
public void requestMappingHandlerAdapter() throws Exception {
RequestMappingHandlerAdapter adapter = webConfig.requestMappingHandlerAdapter();
@@ -128,22 +134,30 @@ public class WebMvcConfigurationSupportExtensionTests {
// Message converters
assertEquals(1, adapter.getMessageConverters().size());
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
// Custom argument resolvers and return value handlers
@SuppressWarnings("unchecked")
List<HandlerMethodArgumentResolver> argResolvers= (List<HandlerMethodArgumentResolver>)
new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
List<HandlerMethodArgumentResolver> argResolvers =
(List<HandlerMethodArgumentResolver>) fieldAccessor.getPropertyValue("customArgumentResolvers");
assertEquals(1, argResolvers.size());
@SuppressWarnings("unchecked")
List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>)
new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers");
List<HandlerMethodReturnValueHandler> handlers =
(List<HandlerMethodReturnValueHandler>) fieldAccessor.getPropertyValue("customReturnValueHandlers");
assertEquals(1, handlers.size());
// Async support options
assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass());
assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout"));
assertEquals(ConcurrentTaskExecutor.class, fieldAccessor.getPropertyValue("taskExecutor").getClass());
assertEquals(2500L, fieldAccessor.getPropertyValue("asyncRequestTimeout"));
assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect"));
Map<Object, CallableProcessingInterceptor> callableInterceptors =
(Map<Object, CallableProcessingInterceptor>) fieldAccessor.getPropertyValue("callableInterceptors");
assertEquals(1, callableInterceptors.size());
Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
(Map<Object, DeferredResultProcessingInterceptor>) fieldAccessor.getPropertyValue("deferredResultInterceptors");
assertEquals(1, deferredResultInterceptors.size());
assertEquals(false, fieldAccessor.getPropertyValue("ignoreDefaultModelOnRedirect"));
}
@Test
@@ -240,7 +254,9 @@ public class WebMvcConfigurationSupportExtensionTests {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(2500).setTaskExecutor(new ConcurrentTaskExecutor());
configurer.setDefaultTimeout(2500).setTaskExecutor(new ConcurrentTaskExecutor())
.registerCallableInterceptors(new CallableProcessingInterceptorAdapter() { })
.registerDeferredResultInterceptors(new DeferredResultProcessingInterceptorAdapter() {});
}
@Override

View File

@@ -5,7 +5,14 @@
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<mvc:annotation-driven>
<mvc:async-support default-timeout="2500" task-executor="executor" />
<mvc:async-support default-timeout="2500" task-executor="executor">
<mvc:callable-interceptors>
<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestCallableProcessingInterceptor" />
</mvc:callable-interceptors>
<mvc:deferred-result-interceptors>
<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestDeferredResultProcessingInterceptor" />
</mvc:deferred-result-interceptors>
</mvc:async-support>
</mvc:annotation-driven>
<bean id="executor" class="org.springframework.scheduling.concurrent.ConcurrentTaskExecutor" />

View File

@@ -32,6 +32,7 @@ Changes in version 3.2 RC1 (2012-10-29)
* added CallableProcessingInterceptor and DeferredResultProcessingInterceptor
* added support for wildcard media types in AbstractView and ContentNegotiationViewResolver (SPR-9807)
* the jackson message converters now include "application/*+json" in supported media types (SPR-7905)
* add options to configure MVC async interceptors in the MVC namespace and Java config (SPR-9914)
Changes in version 3.2 M2 (2012-09-11)
--------------------------------------