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
This commit is contained in:
Rossen Stoyanchev 2016-01-29 14:49:25 -05:00
parent ea46ad8022
commit 1cf0fb8174
4 changed files with 145 additions and 27 deletions

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver); addResponseBodyAdvice(exceptionHandlerExceptionResolver);
if (argumentResolvers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver); String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class); RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);

View File

@ -199,6 +199,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private ContentNegotiationManager contentNegotiationManager; private ContentNegotiationManager contentNegotiationManager;
private List<HandlerMethodArgumentResolver> argumentResolvers;
private List<HandlerMethodReturnValueHandler> returnValueHandlers;
private List<HttpMessageConverter<?>> messageConverters; private List<HttpMessageConverter<?>> messageConverters;
private Map<String, CorsConfiguration> corsConfigurations; private Map<String, CorsConfiguration> corsConfigurations;
@ -474,18 +478,12 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
*/ */
@Bean @Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
addArgumentResolvers(argumentResolvers);
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
addReturnValueHandlers(returnValueHandlers);
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager()); adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters()); adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(argumentResolvers); adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(returnValueHandlers); adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) { if (jackson2Present) {
List<RequestBodyAdvice> requestBodyAdvices = new ArrayList<RequestBodyAdvice>(); List<RequestBodyAdvice> requestBodyAdvices = new ArrayList<RequestBodyAdvice>();
@ -625,6 +623,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
return null; 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<HandlerMethodArgumentResolver> getArgumentResolvers() {
if (this.argumentResolvers == null) {
this.argumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
addArgumentResolvers(this.argumentResolvers);
}
return this.argumentResolvers;
}
/** /**
* Add custom {@link HandlerMethodArgumentResolver}s to use in addition to * Add custom {@link HandlerMethodArgumentResolver}s to use in addition to
* the ones registered by default. * the ones registered by default.
@ -639,6 +651,20 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> 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<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
if (this.returnValueHandlers == null) {
this.returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
addReturnValueHandlers(this.returnValueHandlers);
}
return this.returnValueHandlers;
}
/** /**
* Add custom {@link HandlerMethodReturnValueHandler}s in addition to the * Add custom {@link HandlerMethodReturnValueHandler}s in addition to the
* ones registered by default. * ones registered by default.
@ -821,6 +847,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) { if (jackson2Present) {
List<ResponseBodyAdvice<?>> interceptors = new ArrayList<ResponseBodyAdvice<?>>(); List<ResponseBodyAdvice<?>> interceptors = new ArrayList<ResponseBodyAdvice<?>>();
interceptors.add(new JsonViewResponseBodyAdvice()); interceptors.add(new JsonViewResponseBodyAdvice());

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); verifyMessageConverters(appContext.getBean(ExceptionHandlerExceptionResolver.class), false);
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testArgumentResolvers() { public void testArgumentResolvers() {
loadBeanDefinitions("mvc-config-argument-resolvers.xml"); loadBeanDefinitions("mvc-config-argument-resolvers.xml");
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); testArgumentResolvers(this.appContext.getBean(RequestMappingHandlerAdapter.class));
assertNotNull(adapter); testArgumentResolvers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class));
Object value = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers"); }
private void testArgumentResolvers(Object bean) {
assertNotNull(bean);
Object value = new DirectFieldAccessor(bean).getPropertyValue("customArgumentResolvers");
assertNotNull(value); assertNotNull(value);
assertTrue(value instanceof List); assertTrue(value instanceof List);
@SuppressWarnings("unchecked")
List<HandlerMethodArgumentResolver> resolvers = (List<HandlerMethodArgumentResolver>) value; List<HandlerMethodArgumentResolver> resolvers = (List<HandlerMethodArgumentResolver>) value;
assertEquals(3, resolvers.size()); assertEquals(3, resolvers.size());
assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter); assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter);
@ -126,15 +130,19 @@ public class AnnotationDrivenBeanDefinitionParserTests {
assertNotSame(resolvers.get(1), resolvers.get(2)); assertNotSame(resolvers.get(1), resolvers.get(2));
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testReturnValueHandlers() { public void testReturnValueHandlers() {
loadBeanDefinitions("mvc-config-return-value-handlers.xml"); loadBeanDefinitions("mvc-config-return-value-handlers.xml");
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class); testReturnValueHandlers(this.appContext.getBean(RequestMappingHandlerAdapter.class));
assertNotNull(adapter); testReturnValueHandlers(this.appContext.getBean(ExceptionHandlerExceptionResolver.class));
Object value = new DirectFieldAccessor(adapter).getPropertyValue("customReturnValueHandlers"); }
private void testReturnValueHandlers(Object bean) {
assertNotNull(bean);
Object value = new DirectFieldAccessor(bean).getPropertyValue("customReturnValueHandlers");
assertNotNull(value); assertNotNull(value);
assertTrue(value instanceof List); assertTrue(value instanceof List);
@SuppressWarnings("unchecked")
List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>) value; List<HandlerMethodReturnValueHandler> handlers = (List<HandlerMethodReturnValueHandler>) value;
assertEquals(2, handlers.size()); assertEquals(2, handlers.size());
assertEquals(TestHandlerMethodReturnValueHandler.class, handlers.get(0).getClass()); assertEquals(TestHandlerMethodReturnValueHandler.class, handlers.get(0).getClass());

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.List;
import java.util.Locale; import java.util.Locale;
import javax.servlet.http.HttpServletRequest; 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.joda.time.DateTime;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor; 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.annotation.ScopedProxyMode;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.StaticMessageSource; import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.format.annotation.DateTimeFormat; 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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; 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.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.method.support.CompositeUriComponentsContributor; 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.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.ViewResolver; 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.servlet.view.ViewResolverComposite;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
import com.fasterxml.jackson.databind.DeserializationFeature; import static org.junit.Assert.assertEquals;
import com.fasterxml.jackson.databind.MapperFeature; import static org.junit.Assert.assertFalse;
import com.fasterxml.jackson.databind.ObjectMapper; import static org.junit.Assert.assertNotNull;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/** /**
* Integration tests for {@link WebMvcConfigurationSupport} (imported via * 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 @Test
public void mvcViewResolver() { public void mvcViewResolver() {
ApplicationContext context = initContext(WebConfig.class); ApplicationContext context = initContext(WebConfig.class);
@ -336,6 +369,21 @@ public class WebMvcConfigurationSupportTests {
} }
} }
@EnableWebMvc
@Configuration
static class CustomArgumentResolverConfig extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TestArgumentResolver());
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(new TestReturnValueHandler());
}
}
@Controller @Controller
public static class TestController { public static class TestController {
@ -377,4 +425,31 @@ public class WebMvcConfigurationSupportTests {
public static class UserAlreadyExistsException extends RuntimeException { 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) {
}
}
} }