Browse Source

DATACMNS-1237 - Register additional ProxyingHandlerMethodArgumentResolver as first resolver.

We now deploy a custom BeanPostProcessor to customize RequestMappingHandlerAdapter instances by prepending a ProxyingHandlerMethodArgumentResolver (requiring a @ModelAttribute) to the list of resolved HandlerMethodArgumentResolvers to make sure the settings defined in the annotation are applied but the projecting way of data binding is still used.
pull/271/head
Oliver Gierke 8 years ago
parent
commit
05de55ffba
  1. 27
      src/main/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolver.java
  2. 2
      src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java
  3. 143
      src/main/java/org/springframework/data/web/config/ProjectingArgumentResolverRegistrar.java
  4. 3
      src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java
  5. 2
      src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java
  6. 11
      src/test/java/org/springframework/data/web/config/EnableSpringDataWebSupportIntegrationTests.java

27
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.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
@ -46,16 +48,29 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP
private static final List<String> IGNORED_PACKAGES = Arrays.asList("java", "org.springframework"); private static final List<String> IGNORED_PACKAGES = Arrays.asList("java", "org.springframework");
private final SpelAwareProxyProjectionFactory proxyFactory; private final SpelAwareProxyProjectionFactory proxyFactory;
private final ConversionService conversionService; private final ObjectFactory<ConversionService> 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}. * Creates a new {@link PageableHandlerMethodArgumentResolver} using the given {@link ConversionService}.
* *
* @param conversionService must not be {@literal null}. * @param conversionService must not be {@literal null}.
*/ */
public ProxyingHandlerMethodArgumentResolver(ConversionService conversionService) { public ProxyingHandlerMethodArgumentResolver(ObjectFactory<ConversionService> conversionService,
boolean annotationNotRequired) {
super(true); super(annotationNotRequired);
this.proxyFactory = new SpelAwareProxyProjectionFactory(); this.proxyFactory = new SpelAwareProxyProjectionFactory();
this.conversionService = conversionService; this.conversionService = conversionService;
@ -86,6 +101,10 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP
@Override @Override
public boolean supportsParameter(MethodParameter parameter) { public boolean supportsParameter(MethodParameter parameter) {
if (!super.supportsParameter(parameter)) {
return false;
}
Class<?> type = parameter.getParameterType(); Class<?> type = parameter.getParameterType();
if (!type.isInterface()) { if (!type.isInterface()) {
@ -116,7 +135,7 @@ public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodP
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception { 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())); binder.bind(new MutablePropertyValues(request.getParameterMap()));
return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget()); return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());

2
src/main/java/org/springframework/data/web/config/EnableSpringDataWebSupport.java

@ -103,6 +103,8 @@ public @interface EnableSpringDataWebSupport {
List<String> imports = new ArrayList<>(); List<String> imports = new ArrayList<>();
imports.add(ProjectingArgumentResolverRegistrar.class.getName());
imports.add(resourceLoader// imports.add(resourceLoader//
.filter(it -> ClassUtils.isPresent("org.springframework.hateoas.Link", it))// .filter(it -> ClassUtils.isPresent("org.springframework.hateoas.Link", it))//
.map(it -> HateoasAwareSpringDataWebConfiguration.class.getName())// .map(it -> HateoasAwareSpringDataWebConfiguration.class.getName())//

143
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> 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> 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<HandlerMethodArgumentResolver> currentResolvers = adapter.getArgumentResolvers();
if (currentResolvers == null) {
throw new IllegalStateException(
String.format("No HandlerMethodArgumentResolvers found in RequestMappingHandlerAdapter %s!", beanName));
}
List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<HandlerMethodArgumentResolver>(
currentResolvers.size() + 1);
newResolvers.add(resolver);
newResolvers.addAll(currentResolvers);
adapter.setArgumentResolvers(newResolvers);
return adapter;
}
}
}

3
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(sortResolver());
argumentResolvers.add(pageableResolver()); argumentResolvers.add(pageableResolver());
ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(conversionService, true);
conversionService.getObject());
resolver.setBeanFactory(context); resolver.setBeanFactory(context);
forwardBeanClassLoader(resolver); forwardBeanClassLoader(resolver);

2
src/test/java/org/springframework/data/web/ProxyingHandlerMethodArgumentResolverUnitTests.java

@ -35,7 +35,7 @@ import org.springframework.data.web.ProjectingJackson2HttpMessageConverterUnitTe
public class ProxyingHandlerMethodArgumentResolverUnitTests { public class ProxyingHandlerMethodArgumentResolverUnitTests {
ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver( ProxyingHandlerMethodArgumentResolver resolver = new ProxyingHandlerMethodArgumentResolver(
new DefaultConversionService()); () -> new DefaultConversionService(), true);
@Test // DATACMNS-776 @Test // DATACMNS-776
public void supportAnnotatedInterface() throws Exception { public void supportAnnotatedInterface() throws Exception {

11
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.geo.Point;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver; import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver; import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.WebTestUtils; import org.springframework.data.web.WebTestUtils;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
@ -204,6 +205,16 @@ public class EnableSpringDataWebSupportIntegrationTests {
assertThat((String) ReflectionTestUtils.getField(resolver, "sortParameter")).isEqualTo("foo"); 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) { private static void assertResolversRegistered(ApplicationContext context, Class<?>... resolverTypes) {
RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);

Loading…
Cancel
Save