Skip to content

Commit e14ba9d

Browse files
committed
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
1 parent e4a13cd commit e14ba9d

File tree

10 files changed

+240
-16
lines changed

10 files changed

+240
-16
lines changed

spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ public void registerCallableInterceptor(Object key, CallableProcessingIntercepto
175175
this.callableInterceptors.put(key, interceptor);
176176
}
177177

178+
public void registerAllCallableInterceptors(Map<Object, CallableProcessingInterceptor> interceptors) {
179+
Assert.notNull(interceptors);
180+
this.callableInterceptors.putAll(interceptors);
181+
}
182+
178183
/**
179184
* Register a {@link DeferredResultProcessingInterceptor} that will be
180185
* applied when concurrent request handling with a {@link DeferredResult}
@@ -188,6 +193,11 @@ public void registerDeferredResultInterceptor(Object key, DeferredResultProcessi
188193
this.deferredResultInterceptors.put(key, interceptor);
189194
}
190195

196+
public void registerAllDeferredResultInterceptors(Map<Object, DeferredResultProcessingInterceptor> interceptors) {
197+
Assert.notNull(interceptors);
198+
this.deferredResultInterceptors.putAll(interceptors);
199+
}
200+
191201
/**
192202
* Clear {@linkplain #getConcurrentResult() concurrentResult} and
193203
* {@linkplain #getConcurrentResultContext() concurrentResultContext}.

spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
174174
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, source, parserContext);
175175
String asyncTimeout = getAsyncTimeout(element, source, parserContext);
176176
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element, source, parserContext);
177+
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
178+
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
177179

178180
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
179181
handlerAdapterDef.setSource(source);
@@ -197,6 +199,8 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
197199
if (asyncExecutor != null) {
198200
handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
199201
}
202+
handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
203+
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
200204
String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef);
201205

202206
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
@@ -337,6 +341,40 @@ private RuntimeBeanReference getAsyncExecutor(Element element, Object source, Pa
337341
return null;
338342
}
339343

344+
private ManagedList<?> getCallableInterceptors(Element element, Object source, ParserContext parserContext) {
345+
ManagedList<? super Object> interceptors = new ManagedList<Object>();
346+
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
347+
if (asyncElement != null) {
348+
Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "callable-interceptors");
349+
if (interceptorsElement != null) {
350+
interceptors.setSource(source);
351+
for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
352+
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
353+
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
354+
interceptors.add(beanDef);
355+
}
356+
}
357+
}
358+
return interceptors;
359+
}
360+
361+
private ManagedList<?> getDeferredResultInterceptors(Element element, Object source, ParserContext parserContext) {
362+
ManagedList<? super Object> interceptors = new ManagedList<Object>();
363+
Element asyncElement = DomUtils.getChildElementByTagName(element, "async-support");
364+
if (asyncElement != null) {
365+
Element interceptorsElement = DomUtils.getChildElementByTagName(asyncElement, "deferred-result-interceptors");
366+
if (interceptorsElement != null) {
367+
interceptors.setSource(source);
368+
for (Element converter : DomUtils.getChildElementsByTagName(interceptorsElement, "bean")) {
369+
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
370+
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
371+
interceptors.add(beanDef);
372+
}
373+
}
374+
}
375+
return interceptors;
376+
}
377+
340378
private ManagedList<?> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
341379
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
342380
if (resolversElement != null) {

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,18 @@
1515
*/
1616
package org.springframework.web.servlet.config.annotation;
1717

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.List;
1821
import java.util.concurrent.Callable;
1922

2023
import org.springframework.core.task.AsyncTaskExecutor;
2124
import org.springframework.core.task.SimpleAsyncTaskExecutor;
25+
import org.springframework.util.Assert;
2226
import org.springframework.web.context.request.async.AsyncTask;
27+
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
28+
import org.springframework.web.context.request.async.DeferredResult;
29+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
2330

2431
/**
2532
* Helps with configuring a options for asynchronous request processing.
@@ -33,6 +40,13 @@ public class AsyncSupportConfigurer {
3340

3441
private Long timeout;
3542

43+
private final List<CallableProcessingInterceptor> callableInterceptors =
44+
new ArrayList<CallableProcessingInterceptor>();
45+
46+
private final List<DeferredResultProcessingInterceptor> deferredResultInterceptors =
47+
new ArrayList<DeferredResultProcessingInterceptor>();
48+
49+
3650
/**
3751
* Set the default {@link AsyncTaskExecutor} to use when a controller method
3852
* returns a {@link Callable}. Controller methods can override this default on
@@ -64,6 +78,31 @@ public AsyncSupportConfigurer setDefaultTimeout(long timeout) {
6478
return this;
6579
}
6680

81+
/**
82+
* Configure lifecycle intercepters with callbacks around concurrent request
83+
* execution that starts when a controller returns a
84+
* {@link java.util.concurrent.Callable}.
85+
*
86+
* @param interceptors the interceptors to register
87+
*/
88+
public AsyncSupportConfigurer registerCallableInterceptors(CallableProcessingInterceptor... interceptors) {
89+
Assert.notNull(interceptors, "Interceptors are required");
90+
this.callableInterceptors.addAll(Arrays.asList(interceptors));
91+
return this;
92+
}
93+
94+
/**
95+
* Configure lifecycle intercepters with callbacks around concurrent request
96+
* execution that starts when a controller returns a {@link DeferredResult}.
97+
*
98+
* @param interceptors the interceptors to register
99+
*/
100+
public AsyncSupportConfigurer registerDeferredResultInterceptors(DeferredResultProcessingInterceptor... interceptors) {
101+
Assert.notNull(interceptors, "Interceptors are required");
102+
this.deferredResultInterceptors.addAll(Arrays.asList(interceptors));
103+
return this;
104+
}
105+
67106
protected AsyncTaskExecutor getTaskExecutor() {
68107
return this.taskExecutor;
69108
}
@@ -72,4 +111,12 @@ protected Long getTimeout() {
72111
return this.timeout;
73112
}
74113

114+
protected List<CallableProcessingInterceptor> getCallableInterceptors() {
115+
return this.callableInterceptors;
116+
}
117+
118+
protected List<DeferredResultProcessingInterceptor> getDeferredResultInterceptors() {
119+
return this.deferredResultInterceptors;
120+
}
121+
75122
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
378378
if (configurer.getTimeout() != null) {
379379
adapter.setAsyncRequestTimeout(configurer.getTimeout());
380380
}
381+
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
382+
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
381383

382384
return adapter;
383385
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
4949
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
5050
import org.springframework.ui.ModelMap;
51+
import org.springframework.util.Assert;
5152
import org.springframework.util.CollectionUtils;
5253
import org.springframework.util.ReflectionUtils.MethodFilter;
5354
import org.springframework.web.accept.ContentNegotiationManager;
@@ -64,6 +65,8 @@
6465
import org.springframework.web.context.request.WebRequest;
6566
import org.springframework.web.context.request.async.AsyncTask;
6667
import org.springframework.web.context.request.async.AsyncWebRequest;
68+
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
69+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
6770
import org.springframework.web.context.request.async.WebAsyncManager;
6871
import org.springframework.web.context.request.async.WebAsyncUtils;
6972
import org.springframework.web.method.ControllerAdviceBean;
@@ -135,6 +138,12 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
135138

136139
private Long asyncRequestTimeout;
137140

141+
private final Map<Object, CallableProcessingInterceptor> callableInterceptors =
142+
new LinkedHashMap<Object, CallableProcessingInterceptor>();
143+
144+
private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
145+
new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();
146+
138147
private boolean ignoreDefaultModelOnRedirect = false;
139148

140149
private int cacheSecondsForSessionAttributeHandlers = 0;
@@ -363,6 +372,34 @@ public void setAsyncRequestTimeout(long timeout) {
363372
this.asyncRequestTimeout = timeout;
364373
}
365374

375+
/**
376+
* Configure {@code CallableProcessingInterceptor}'s to register on async requests.
377+
* @param interceptors the interceptors to register
378+
*/
379+
public void setCallableInterceptors(List<CallableProcessingInterceptor> interceptors) {
380+
Assert.notNull(interceptors);
381+
for (int index = 0 ; index < interceptors.size(); index++) {
382+
CallableProcessingInterceptor interceptor = interceptors.get(index);
383+
this.callableInterceptors.put(getInterceptorKey(interceptor, index), interceptor);
384+
}
385+
}
386+
387+
/**
388+
* Configure {@code DeferredResultProcessingInterceptor}'s to register on async requests.
389+
* @param interceptors the interceptors to register
390+
*/
391+
public void setDeferredResultInterceptors(List<DeferredResultProcessingInterceptor> interceptors) {
392+
Assert.notNull(interceptors);
393+
for (int index = 0 ; index < interceptors.size(); index++) {
394+
DeferredResultProcessingInterceptor interceptor = interceptors.get(index);
395+
this.deferredResultInterceptors.put(getInterceptorKey(interceptor, index), interceptor);
396+
}
397+
}
398+
399+
private String getInterceptorKey(Object interceptor, int index) {
400+
return this.hashCode() + ":" + interceptor.getClass().getName() + "#" + index;
401+
}
402+
366403
/**
367404
* By default the content of the "default" model is used both during
368405
* rendering and redirect scenarios. Alternatively a controller method
@@ -703,6 +740,8 @@ private ModelAndView invokeHandleMethod(HttpServletRequest request,
703740
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
704741
asyncManager.setTaskExecutor(this.taskExecutor);
705742
asyncManager.setAsyncWebRequest(asyncWebRequest);
743+
asyncManager.registerAllCallableInterceptors(this.callableInterceptors);
744+
asyncManager.registerAllDeferredResultInterceptors(this.deferredResultInterceptors);
706745

707746
if (asyncManager.hasConcurrentResult()) {
708747
Object result = asyncManager.getConcurrentResult();

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.2.xsd

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,46 @@
9797
]]></xsd:documentation>
9898
</xsd:annotation>
9999
<xsd:complexType>
100+
<xsd:all minOccurs="0">
101+
<xsd:element name="callable-interceptors" minOccurs="0">
102+
<xsd:annotation>
103+
<xsd:documentation><![CDATA[
104+
The ordered set of interceptors that intercept the lifecycle of concurrently executed
105+
requests, which start after a controller returns a java.util.concurrent.Callable.
106+
]]></xsd:documentation>
107+
</xsd:annotation>
108+
<xsd:complexType>
109+
<xsd:sequence>
110+
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
111+
<xsd:annotation>
112+
<xsd:documentation><![CDATA[
113+
Registers a CallableProcessingInterceptor.
114+
]]></xsd:documentation>
115+
</xsd:annotation>
116+
</xsd:element>
117+
</xsd:sequence>
118+
</xsd:complexType>
119+
</xsd:element>
120+
<xsd:element name="deferred-result-interceptors" minOccurs="0">
121+
<xsd:annotation>
122+
<xsd:documentation><![CDATA[
123+
The ordered set of interceptors that intercept the lifecycle of concurrently executed
124+
requests, which start after a controller returns a DeferredResult.
125+
]]></xsd:documentation>
126+
</xsd:annotation>
127+
<xsd:complexType>
128+
<xsd:sequence>
129+
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
130+
<xsd:annotation>
131+
<xsd:documentation><![CDATA[
132+
Registers a DeferredResultProcessingInterceptor.
133+
]]></xsd:documentation>
134+
</xsd:annotation>
135+
</xsd:element>
136+
</xsd:sequence>
137+
</xsd:complexType>
138+
</xsd:element>
139+
</xsd:all>
100140
<xsd:attribute name="task-executor" type="xsd:string">
101141
<xsd:annotation>
102142
<xsd:documentation source="java:org.springframework.core.task.AsyncTaskExecutor"><![CDATA[

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,27 @@
1616

1717
package org.springframework.web.servlet.config;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertNull;
23+
import static org.junit.Assert.assertSame;
24+
import static org.junit.Assert.assertTrue;
25+
1926
import java.lang.annotation.Retention;
2027
import java.lang.annotation.RetentionPolicy;
2128
import java.lang.reflect.Method;
2229
import java.util.Arrays;
2330
import java.util.Date;
2431
import java.util.List;
2532
import java.util.Locale;
33+
import java.util.Map;
34+
2635
import javax.servlet.RequestDispatcher;
2736
import javax.validation.constraints.NotNull;
2837

2938
import org.junit.Before;
3039
import org.junit.Test;
31-
3240
import org.springframework.beans.DirectFieldAccessor;
3341
import org.springframework.beans.TypeMismatchException;
3442
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
@@ -57,6 +65,10 @@
5765
import org.springframework.web.bind.annotation.RequestParam;
5866
import org.springframework.web.context.request.NativeWebRequest;
5967
import org.springframework.web.context.request.ServletWebRequest;
68+
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
69+
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter;
70+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
71+
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
6072
import org.springframework.web.context.support.GenericWebApplicationContext;
6173
import org.springframework.web.method.HandlerMethod;
6274
import org.springframework.web.method.support.InvocableHandlerMethod;
@@ -76,8 +88,6 @@
7688
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
7789
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
7890

79-
import static org.junit.Assert.*;
80-
8191
/**
8292
* @author Keith Donald
8393
* @author Arjen Poutsma
@@ -469,8 +479,18 @@ public void testAsyncSupportOptions() throws Exception {
469479

470480
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
471481
assertNotNull(adapter);
472-
assertEquals(ConcurrentTaskExecutor.class, new DirectFieldAccessor(adapter).getPropertyValue("taskExecutor").getClass());
473-
assertEquals(2500L, new DirectFieldAccessor(adapter).getPropertyValue("asyncRequestTimeout"));
482+
483+
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);
484+
assertEquals(ConcurrentTaskExecutor.class, fieldAccessor.getPropertyValue("taskExecutor").getClass());
485+
assertEquals(2500L, fieldAccessor.getPropertyValue("asyncRequestTimeout"));
486+
487+
Map<Object, CallableProcessingInterceptor> callableInterceptors =
488+
(Map<Object, CallableProcessingInterceptor>) fieldAccessor.getPropertyValue("callableInterceptors");
489+
assertEquals(1, callableInterceptors.size());
490+
491+
Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
492+
(Map<Object, DeferredResultProcessingInterceptor>) fieldAccessor.getPropertyValue("deferredResultInterceptors");
493+
assertEquals(1, deferredResultInterceptors.size());
474494
}
475495

476496

@@ -539,4 +559,8 @@ public RequestDispatcher getNamedDispatcher(String path) {
539559
}
540560
}
541561

562+
public static class TestCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { }
563+
564+
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }
565+
542566
}

0 commit comments

Comments
 (0)