From 1cf0fb8174972cd44122b905f763c81902300517 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 29 Jan 2016 14:49:25 -0500 Subject: [PATCH] Shared custom argument resolvers Custom argument resolvers configured in the MVC Java config or the MVC namespace are now injected in both the RequestMappingHandlerAdapter as well as in the ExceptionHandlerExceptionResolver. Issue: SPR-12058 --- .../AnnotationDrivenBeanDefinitionParser.java | 9 +- .../WebMvcConfigurationSupport.java | 44 +++++++-- ...tationDrivenBeanDefinitionParserTests.java | 26 ++++-- .../WebMvcConfigurationSupportTests.java | 93 +++++++++++++++++-- 4 files changed, 145 insertions(+), 27 deletions(-) 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 7c8cbcb4718..dd80a15a9a9 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 674d8211113..72b2afc1e3b 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 322acd675b9..61e6f441a0b 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 f716b3fc3ac..5125cc42b19 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) { + } + } + }