diff --git a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java index 2831b502a..9ab8a76f6 100644 --- a/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java @@ -23,12 +23,14 @@ 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.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.convert.ConversionService; 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; @@ -46,16 +48,29 @@ 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(() -> 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; @@ -86,6 +101,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()) { @@ -116,7 +135,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 c8493c335..ff3aa6ec2 100644 --- a/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java +++ b/src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java @@ -103,6 +103,8 @@ public @interface EnableSpringDataWebSupport { List imports = new ArrayList<>(); + imports.add(ProjectingArgumentResolverRegistrar.class.getName()); + imports.add(resourceLoader// .filter(it -> ClassUtils.isPresent("org.springframework.hateoas.Link", it))// .map(it -> HateoasAwareSpringDataWebConfiguration.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..99e4b4b7b --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.java @@ -0,0 +1,143 @@ +/* + * 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.lang.Nullable; +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 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) + */ + @Nullable + @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) + */ + @Nullable + @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(); + + if (currentResolvers == null) { + throw new IllegalStateException( + String.format("No HandlerMethodArgumentResolvers found in RequestMappingHandlerAdapter %s!", beanName)); + } + + 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 799d04257..c7beccf48 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java @@ -140,8 +140,7 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo argumentResolvers.add(sortResolver()); argumentResolvers.add(pageableResolver()); - ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( - conversionService.getObject()); + ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true); resolver.setBeanFactory(context); forwardBeanClassLoader(resolver); diff --git a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java index 79da290ee..0f5401b52 100755 --- a/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java @@ -35,7 +35,7 @@ import org.springframework.data.web.ProjectingJackson2HttpMessageConverterUnitTe public class ProxyingHandlerMethodArgumentResolverUnitTests { ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( - new DefaultConversionService()); + () -> new DefaultConversionService(), 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 f039284f6..9e1035e44 100755 --- a/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java +++ b/src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java @@ -32,6 +32,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; @@ -204,6 +205,16 @@ public class EnableSpringDataWebSupportIntegrationTests { assertThat((String) ReflectionTestUtils.getField(resolver, "sortParameter")).isEqualTo("foo"); } + @Test // DATACMNS-1237 + public void configuresProxyingHandlerMethodArgumentResolver() { + + ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class); + + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); + + assertThat(adapter.getArgumentResolvers().get(0)).isInstanceOf(ProxyingHandlerMethodArgumentResolver.class); + } + private static void assertResolversRegistered(ApplicationContext context, Class... resolverTypes) { RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);