diff --git a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java index 0ab0c9441..042785aea 100644 --- a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java @@ -23,6 +23,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; @@ -31,6 +32,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.util.ClassUtils; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; @@ -48,16 +50,36 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP private static final List IGNORED_PACKAGES = Arrays.asList("java", "org.springframework"); private final SpelAwareProxyProjectionFactory proxyFactory; - private final ConversionService conversionService; + private final ObjectFactory conversionService; + + /** + * Creates a new {@link PageableHandlerMethodArgumentResolver} using the given {@link ConversionService} and the + * {@link ModelAttribute} annotation not required. + * + * @param conversionService must not be {@literal null}. + * @deprecated use {@link #ProxyingHandlerMethodArgumentResolver(ObjectFactory, boolean)} instead. + */ + @Deprecated + public ProxyingHandlerMethodArgumentResolver(final ConversionService conversionService) { + + this(new ObjectFactory() { + + @Override + public ConversionService getObject() throws BeansException { + return conversionService; + } + }, true); + } /** * Creates a new {@link PageableHandlerMethodArgumentResolver} using the given {@link ConversionService}. * * @param conversionService must not be {@literal null}. */ - public ProxyingHandlerMethodArgumentResolver(ConversionService conversionService) { + public ProxyingHandlerMethodArgumentResolver(ObjectFactory conversionService, + boolean annotationNotRequired) { - super(true); + super(annotationNotRequired); this.proxyFactory = new SpelAwareProxyProjectionFactory(); this.conversionService = conversionService; @@ -98,6 +120,10 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP @Override public boolean supportsParameter(MethodParameter parameter) { + if (!super.supportsParameter(parameter)) { + return false; + } + Class type = parameter.getParameterType(); if (!type.isInterface()) { @@ -132,7 +158,7 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { - MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService); + MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService.getObject()); binder.bind(new MutablePropertyValues(request.getParameterMap())); return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget()); diff --git a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java index 065aa2b37..d70699ec7 100644 --- a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java +++ b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java @@ -115,6 +115,8 @@ public @interface EnableSpringDataWebSupport { List imports = new ArrayList(); + imports.add(ProjectingArgumentResolverRegistrar.class.getName()); + imports.add(ClassUtils.isPresent("org.springframework.hateoas.Link", resourceLoader.getClassLoader()) ? HateoasAwareSpringDataWebConfiguration.class.getName() : SpringDataWebConfiguration.class.getName()); diff --git a/src/main/java/org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.java b/src/main/java/org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.java new file mode 100644 index 000000000..567397206 --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.java @@ -0,0 +1,135 @@ +/* + * Copyright 2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.web.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; + +/** + * Configuration class to register a {@link BeanPostProcessor} to augment {@link RequestMappingHandlerAdapter} with a + * {@link ProxyingHandlerMethodArgumentResolver}. + * + * @author Oliver Gierke + * @soundtrack Apparat With Soap & Skin - Goodbye (Dark Theme Song - https://www.youtube.com/watch?v=66VnOdk6oto) + */ +@Configuration +public class ProjectingArgumentResolverRegistrar { + + /** + * Registers a {@link BeanPostProcessor} to modify {@link RequestMappingHandlerAdapter} beans in the application + * context to get a {@link ProxyingHandlerMethodArgumentResolver} configured as first + * {@link HandlerMethodArgumentResolver}. + * + * @param conversionService the Spring MVC {@link ConversionService} in a lazy fashion, so that its initialization is + * not triggered yet. + * @return + */ + @Bean + public static ProjectingArgumentResolverBeanPostProcessor projectingArgumentResolverBeanPostProcessor( + @Qualifier("mvcConversionService") ObjectFactory conversionService) { + return new ProjectingArgumentResolverBeanPostProcessor(conversionService); + } + + /** + * A {@link BeanPostProcessor} to modify {@link RequestMappingHandlerAdapter} beans in the application context to get + * a {@link ProxyingHandlerMethodArgumentResolver} configured as first {@link HandlerMethodArgumentResolver}. + * + * @author Oliver Gierke + * @soundtrack Apparat With Soap & Skin - Goodbye (Dark Theme Song - https://www.youtube.com/watch?v=66VnOdk6oto) + */ + private static class ProjectingArgumentResolverBeanPostProcessor + implements BeanPostProcessor, BeanFactoryAware, BeanClassLoaderAware { + + private final ProxyingHandlerMethodArgumentResolver resolver; + + /** + * A {@link BeanPostProcessor} to modify {@link RequestMappingHandlerAdapter} beans in the application context to + * get a {@link ProxyingHandlerMethodArgumentResolver} configured as first {@link HandlerMethodArgumentResolver}. + * + * @param conversionService the Spring MVC {@link ConversionService} in a lazy fashion, so that its initialization + * is not triggered yet. + */ + public ProjectingArgumentResolverBeanPostProcessor( + @Qualifier("mvcConversionService") ObjectFactory conversionService) { + this.resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, false); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory) + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.resolver.setBeanFactory(beanFactory); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.resolver.setBeanClassLoader(classLoader); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) + */ + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String) + */ + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + if (!RequestMappingHandlerAdapter.class.isInstance(bean)) { + return bean; + } + + RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; + List currentResolvers = adapter.getArgumentResolvers(); + + List newResolvers = new ArrayList( + currentResolvers.size() + 1); + newResolvers.add(resolver); + newResolvers.addAll(currentResolvers); + + adapter.setArgumentResolvers(newResolvers); + + return adapter; + } + } +} diff --git a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java index fcf249c47..ef4c6f7f3 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -105,8 +105,7 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter { argumentResolvers.add(sortResolver()); argumentResolvers.add(pageableResolver()); - ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( - conversionService.getObject()); + ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true); resolver.setBeanFactory(context); resolver.setBeanClassLoader(context.getClassLoader()); diff --git a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java index 276e43c8b..84fa2fcde 100644 --- a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java @@ -22,7 +22,9 @@ import java.lang.reflect.Method; import java.util.List; import org.junit.Test; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.web.ProjectingJackson2HttpMessageConverterUnitTests.SampleInterface; @@ -35,8 +37,15 @@ import org.springframework.data.web.ProjectingJackson2HttpMessageConverterUnitTe */ public class ProxyingHandlerMethodArgumentResolverUnitTests { - ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( - new DefaultConversionService()); + ObjectFactory conversionService = new ObjectFactory() { + + @Override + public ConversionService getObject() { + return new DefaultConversionService(); + } + }; + + ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true); @Test // DATACMNS-776 public void supportAnnotatedInterface() throws Exception { diff --git a/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java b/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java index cbc53b8bf..7de49af5f 100644 --- a/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java +++ b/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java @@ -35,6 +35,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver; +import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver; import org.springframework.data.web.SortHandlerMethodArgumentResolver; import org.springframework.data.web.WebTestUtils; import org.springframework.hateoas.Link; @@ -162,6 +163,16 @@ public class EnableSpringDataWebSupportIntegrationTests { assertThat(names, hasItem("sampleBean")); } + @Test // DATACMNS-1237 + public void configuresProxyingHandlerMethodArgumentResolver() { + + ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class); + + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); + + assertThat(adapter.getArgumentResolvers().get(0), is(instanceOf(ProxyingHandlerMethodArgumentResolver.class))); + } + @SuppressWarnings("unchecked") private static void assertResolversRegistered(ApplicationContext context, Class... resolverTypes) {