diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index 7c8cbcb471..dd80a15a9a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -286,6 +286,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0); addResponseBodyAdvice(exceptionHandlerExceptionResolver); + if (argumentResolvers != null) { + exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers); + } + if (returnValueHandlers != null) { + exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); + } + String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver); RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 674d821111..72b2afc1e3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -199,6 +199,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv private ContentNegotiationManager contentNegotiationManager; + private List argumentResolvers; + + private List returnValueHandlers; + private List> messageConverters; private Map corsConfigurations; @@ -474,18 +478,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv */ @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { - List argumentResolvers = new ArrayList(); - addArgumentResolvers(argumentResolvers); - - List returnValueHandlers = new ArrayList(); - addReturnValueHandlers(returnValueHandlers); - RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(mvcContentNegotiationManager()); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); - adapter.setCustomArgumentResolvers(argumentResolvers); - adapter.setCustomReturnValueHandlers(returnValueHandlers); + adapter.setCustomArgumentResolvers(getArgumentResolvers()); + adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { List requestBodyAdvices = new ArrayList(); @@ -625,6 +623,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv return null; } + /** + * Provide access to the shared custom argument resolvers used by the + * {@link RequestMappingHandlerAdapter} and the + * {@link ExceptionHandlerExceptionResolver}. This method cannot be + * overridden, use {@link #addArgumentResolvers(List)} instead. + */ + protected final List getArgumentResolvers() { + if (this.argumentResolvers == null) { + this.argumentResolvers = new ArrayList(); + addArgumentResolvers(this.argumentResolvers); + } + return this.argumentResolvers; + } + /** * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to * the ones registered by default. @@ -639,6 +651,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv protected void addArgumentResolvers(List argumentResolvers) { } + /** + * Provide access to the shared return value handlers used by the + * {@link RequestMappingHandlerAdapter} and the + * {@link ExceptionHandlerExceptionResolver}. This method cannot be + * overridden, use {@link #addReturnValueHandlers(List)} instead. + */ + protected final List getReturnValueHandlers() { + if (this.returnValueHandlers == null) { + this.returnValueHandlers = new ArrayList(); + addReturnValueHandlers(this.returnValueHandlers); + } + return this.returnValueHandlers; + } + /** * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the * ones registered by default. @@ -821,6 +847,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); + exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); + exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { List> interceptors = new ArrayList>(); interceptors.add(new JsonViewResponseBodyAdvice()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java index 322acd675b..61e6f441a0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -109,15 +109,19 @@ public class AnnotationDrivenBeanDefinitionParserTests { verifyMessageConverters(appContext.getBean(ExceptionHandlerExceptionResolver.class), false); } - @SuppressWarnings("unchecked") @Test public void testArgumentResolvers() { loadBeanDefinitions("mvc-config-argument-resolvers.xml"); - RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); - assertNotNull(adapter); - Object value = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers"); + testArgumentResolvers(this.appContext.getBean(RequestMappingHandlerAdapter.class)); + testArgumentResolvers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class)); + } + + private void testArgumentResolvers(Object bean) { + assertNotNull(bean); + Object value = new DirectFieldAccessor(bean).getPropertyValue("customArgumentResolvers"); assertNotNull(value); assertTrue(value instanceof List); + @SuppressWarnings("unchecked") List resolvers = (List) value; assertEquals(3, resolvers.size()); assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter); @@ -126,15 +130,19 @@ public class AnnotationDrivenBeanDefinitionParserTests { assertNotSame(resolvers.get(1), resolvers.get(2)); } - @SuppressWarnings("unchecked") @Test public void testReturnValueHandlers() { loadBeanDefinitions("mvc-config-return-value-handlers.xml"); - RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); - assertNotNull(adapter); - Object value = new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers"); + testReturnValueHandlers(this.appContext.getBean(RequestMappingHandlerAdapter.class)); + testReturnValueHandlers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class)); + } + + private void testReturnValueHandlers(Object bean) { + assertNotNull(bean); + Object value = new DirectFieldAccessor(bean).getPropertyValue("customReturnValueHandlers"); assertNotNull(value); assertTrue(value instanceof List); + @SuppressWarnings("unchecked") List handlers = (List) value; assertEquals(2, handlers.size()); assertEquals(TestHandlerMethodReturnValueHandler.class, handlers.get(0).getClass()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index f716b3fc3a..5125cc42b1 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -18,11 +18,13 @@ package org.springframework.web.servlet.config.annotation; import java.util.List; import java.util.Locale; - import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.joda.time.DateTime; - import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; @@ -34,6 +36,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.StaticMessageSource; +import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.convert.ConversionService; import org.springframework.format.annotation.DateTimeFormat; @@ -56,8 +59,13 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.method.support.CompositeUriComponentsContributor; +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.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.ViewResolver; @@ -79,12 +87,11 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.ViewResolverComposite; import org.springframework.web.util.UrlPathHelper; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; - -import static org.junit.Assert.*; +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.assertTrue; /** * Integration tests for {@link WebMvcConfigurationSupport} (imported via @@ -244,6 +251,32 @@ public class WebMvcConfigurationSupportTests { } } + @Test + public void customArgumentResolvers() { + ApplicationContext context = initContext(CustomArgumentResolverConfig.class); + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); + HandlerExceptionResolverComposite composite = context.getBean(HandlerExceptionResolverComposite.class); + + assertNotNull(adapter); + assertEquals(1, adapter.getCustomArgumentResolvers().size()); + assertEquals(TestArgumentResolver.class, adapter.getCustomArgumentResolvers().get(0).getClass()); + assertEquals(1, adapter.getCustomReturnValueHandlers().size()); + assertEquals(TestReturnValueHandler.class, adapter.getCustomReturnValueHandlers().get(0).getClass()); + + assertNotNull(composite); + assertEquals(3, composite.getExceptionResolvers().size()); + assertEquals(ExceptionHandlerExceptionResolver.class, composite.getExceptionResolvers().get(0).getClass()); + + ExceptionHandlerExceptionResolver resolver = + (ExceptionHandlerExceptionResolver) composite.getExceptionResolvers().get(0); + + assertEquals(1, resolver.getCustomArgumentResolvers().size()); + assertEquals(TestArgumentResolver.class, resolver.getCustomArgumentResolvers().get(0).getClass()); + assertEquals(1, resolver.getCustomReturnValueHandlers().size()); + assertEquals(TestReturnValueHandler.class, resolver.getCustomReturnValueHandlers().get(0).getClass()); + } + + @Test public void mvcViewResolver() { ApplicationContext context = initContext(WebConfig.class); @@ -336,6 +369,21 @@ public class WebMvcConfigurationSupportTests { } } + @EnableWebMvc + @Configuration + static class CustomArgumentResolverConfig extends WebMvcConfigurerAdapter { + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(new TestArgumentResolver()); + } + + @Override + public void addReturnValueHandlers(List returnValueHandlers) { + returnValueHandlers.add(new TestReturnValueHandler()); + } + } + @Controller public static class TestController { @@ -377,4 +425,31 @@ public class WebMvcConfigurationSupportTests { public static class UserAlreadyExistsException extends RuntimeException { } + private static class TestArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return false; + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container, + NativeWebRequest request, WebDataBinderFactory factory) { + return null; + } + } + + private static class TestReturnValueHandler implements HandlerMethodReturnValueHandler { + + @Override + public boolean supportsReturnType(MethodParameter returnType) { + return false; + } + + @Override + public void handleReturnValue(Object value, MethodParameter parameter, + ModelAndViewContainer container, NativeWebRequest request) { + } + } + }