Add support to reference external HandlerMethodArgumentResolver beans which might contain already configured instances (e.g. through a 3rd-party namespace handler).

Users can not mix and match between "inner bean" argument resolver and "external bean" argument resolver. This commit only focuses only on argument-resolver, while the support could be extended to return value handlers as well.

 Issue: SPR-11927
This commit is contained in:
Agim Emruli 2014-07-06 22:29:03 +02:00 committed by Rossen Stoyanchev
parent a4484bb767
commit 19760f9eb9
4 changed files with 79 additions and 7 deletions

View File

@ -19,14 +19,14 @@ package org.springframework.web.servlet.config;
import java.util.List;
import java.util.Properties;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
@ -44,11 +44,13 @@ import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.accept.ContentNegotiationManager;
@ -69,6 +71,7 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@ -138,6 +141,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Agim Emruli
* @since 3.0
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@ -468,7 +472,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
if (resolversElement != null) {
ManagedList<BeanDefinitionHolder> argumentResolvers = extractBeanSubElements(resolversElement, parserContext);
return wrapWebArgumentResolverBeanDefs(argumentResolvers, parserContext);
ManagedList<BeanMetadataElement> customAndReferencedResolvers = new ManagedList<BeanMetadataElement>();
customAndReferencedResolvers.addAll(wrapWebArgumentResolverBeanDefs(argumentResolvers, parserContext));
customAndReferencedResolvers.addAll(extractBeanRefSubElements(resolversElement, parserContext));
return customAndReferencedResolvers;
}
return null;
}
@ -540,6 +547,25 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
return list;
}
private ManagedList<BeanReference> extractBeanRefSubElements(Element parentElement, ParserContext parserContext){
ManagedList<BeanReference> list = new ManagedList<BeanReference>();
list.setSource(parserContext.extractSource(parentElement));
for (Element refElement : DomUtils.getChildElementsByTagName(parentElement, "ref")) {
BeanReference reference;
if (StringUtils.hasText("bean")) {
reference = new RuntimeBeanReference(refElement.getAttribute("bean"),false);
list.add(reference);
}else if(StringUtils.hasText("parent")){
reference = new RuntimeBeanReference(refElement.getAttribute("parent"),true);
list.add(reference);
}else{
parserContext.getReaderContext().error("'bean' or 'parent' attribute is required for <ref> element",
parserContext.extractSource(parentElement));
}
}
return list;
}
private ManagedList<BeanDefinitionHolder> wrapWebArgumentResolverBeanDefs(
List<BeanDefinitionHolder> beanDefs, ParserContext parserContext) {

View File

@ -127,15 +127,28 @@
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="beans:bean" minOccurs="1" maxOccurs="unbounded">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="beans:bean" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
The HandlerMethodArgumentResolver (or WebArgumentResolver for backwards compatibility) bean definition.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:element ref="beans:ref" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a HandlerMethodArgumentResolver bean definition. Expects a HandlerMethodArgumentResolver instance,
WebArgumentResolver instances has to be wrapped with a ServletWebArgumentResolverAdapter
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java:org.springframework.web.method.support.HandlerMethodArgumentResolver" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
<xsd:element name="return-value-handlers" minOccurs="0">

View File

@ -38,9 +38,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter;
@ -52,6 +52,7 @@ import static org.junit.Assert.*;
* Test fixture for the configuration in mvc-config-annotation-driven.xml.
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Agim Emruli
*/
public class AnnotationDrivenBeanDefinitionParserTests {
@ -121,6 +122,21 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertTrue(resolvers.get(1) instanceof TestHandlerMethodArgumentResolver);
}
@SuppressWarnings("unchecked")
@Test
public void testArgumentResolversWithReference() {
loadBeanDefinitions("mvc-config-argument-resolvers-references.xml");
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
Object value = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
assertNotNull(value);
assertTrue(value instanceof List);
List<HandlerMethodArgumentResolver> resolvers = (List<HandlerMethodArgumentResolver>) value;
assertEquals(2, resolvers.size());
assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter);
assertTrue(resolvers.get(1) instanceof TestHandlerMethodArgumentResolver);
}
@SuppressWarnings("unchecked")
@Test
public void testReturnValueHandlers() {

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.web.servlet.config.TestWebArgumentResolver"/>
<ref bean="customArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean id="customArgumentResolver" class="org.springframework.web.servlet.config.TestHandlerMethodArgumentResolver"/>
</beans>