Add ArgumentResolverConfigurer

Replace the List<HandlerMethodArgumentResolver> with a dedicated
configurer that currently has one method accepting custom resolver
registrations.
This commit is contained in:
Rossen Stoyanchev 2017-03-29 15:39:31 -04:00
parent 118f33aeda
commit a8162c03f9
9 changed files with 123 additions and 70 deletions

View File

@ -26,7 +26,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/** /**
* A subclass of {@code WebFluxConfigurationSupport} that detects and delegates * A subclass of {@code WebFluxConfigurationSupport} that detects and delegates
@ -70,8 +70,8 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport
} }
@Override @Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
this.configurers.addArgumentResolvers(resolvers); this.configurers.configureArgumentResolvers(configurer);
} }
@Override @Override

View File

@ -16,7 +16,6 @@
package org.springframework.web.reactive.config; package org.springframework.web.reactive.config;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -50,7 +49,7 @@ import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.result.SimpleHandlerAdapter; import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
@ -240,11 +239,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry()); adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry());
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
addArgumentResolvers(resolvers); configureArgumentResolvers(configurer);
if (!resolvers.isEmpty()) { adapter.setArgumentResolverConfigurer(configurer);
adapter.setCustomArgumentResolvers(resolvers);
}
return adapter; return adapter;
} }
@ -257,9 +254,9 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
} }
/** /**
* Provide custom argument resolvers without overriding the built-in ones. * Configure resolvers for custom controller method arguments.
*/ */
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { protected void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
} }
/** /**

View File

@ -29,6 +29,7 @@ import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/** /**
* Defines callback methods to customize the configuration for Web Reactive * Defines callback methods to customize the configuration for Web Reactive
@ -80,11 +81,10 @@ public interface WebFluxConfigurer {
} }
/** /**
* Provide custom controller method argument resolvers. Such resolvers do * Configure resolvers for custom controller method arguments.
* not override and will be invoked after the built-in ones. * @param configurer to configurer to use
* @param resolvers a list of resolvers to add
*/ */
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { default void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
} }
/** /**

View File

@ -28,7 +28,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
/** /**
* A {@link WebFluxConfigurer} that delegates to one or more others. * A {@link WebFluxConfigurer} that delegates to one or more others.
@ -70,8 +70,8 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer {
} }
@Override @Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
this.delegates.forEach(delegate -> delegate.addArgumentResolvers(resolvers)); this.delegates.forEach(delegate -> delegate.configureArgumentResolvers(configurer));
} }
@Override @Override

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-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.web.reactive.result.method.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
/**
* Helps to configure resolvers for Controller method arguments.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class ArgumentResolverConfigurer {
private final List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<>(8);
/**
* Configure resolvers for custom controller method arguments.
* @param resolver the resolver(s) to add
*/
public void addCustomResolver(HandlerMethodArgumentResolver... resolver) {
Assert.notNull(resolver, "'resolvers' must not be null");
this.customResolvers.addAll(Arrays.asList(resolver));
}
List<HandlerMethodArgumentResolver> getCustomResolvers() {
return this.customResolvers;
}
}

View File

@ -17,6 +17,7 @@ package org.springframework.web.reactive.result.method.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -95,34 +96,36 @@ class ControllerMethodResolver {
new LinkedHashMap<>(64); new LinkedHashMap<>(64);
ControllerMethodResolver(List<HandlerMethodArgumentResolver> customResolvers, ControllerMethodResolver(ArgumentResolverConfigurer argumentResolverConfigurer,
List<HttpMessageReader<?>> messageReaders, ReactiveAdapterRegistry reactiveRegistry, List<HttpMessageReader<?>> messageReaders, ReactiveAdapterRegistry reactiveRegistry,
ConfigurableApplicationContext applicationContext) { ConfigurableApplicationContext applicationContext) {
Assert.notNull(customResolvers, "'customResolvers' should not be null"); Assert.notNull(argumentResolverConfigurer, "ArgumentResolverConfigurer is required");
Assert.notNull(reactiveRegistry, "ReactiveAdapterRegistry is required"); Assert.notNull(reactiveRegistry, "ReactiveAdapterRegistry is required");
Assert.notNull(applicationContext, "ConfigurableApplicationContext is required"); Assert.notNull(applicationContext, "ConfigurableApplicationContext is required");
ResolverRegistrar registrar = ResolverRegistrar.customResolvers(customResolvers).basic(); ArgumentResolverRegistrar registrar;
registrar= ArgumentResolverRegistrar.configurer(argumentResolverConfigurer).basic();
addResolversTo(registrar, reactiveRegistry, applicationContext); addResolversTo(registrar, reactiveRegistry, applicationContext);
this.initBinderResolvers = registrar.getSyncResolvers(); this.initBinderResolvers = registrar.getSyncResolvers();
registrar = ResolverRegistrar.customResolvers(customResolvers).modelAttributeSupport(); registrar = ArgumentResolverRegistrar.configurer(argumentResolverConfigurer).modelAttributeSupport();
addResolversTo(registrar, reactiveRegistry, applicationContext); addResolversTo(registrar, reactiveRegistry, applicationContext);
this.modelAttributeResolvers = registrar.getResolvers(); this.modelAttributeResolvers = registrar.getResolvers();
registrar = ResolverRegistrar.customResolvers(customResolvers).fullSupport(messageReaders); registrar = ArgumentResolverRegistrar.configurer(argumentResolverConfigurer).fullSupport(messageReaders);
addResolversTo(registrar, reactiveRegistry, applicationContext); addResolversTo(registrar, reactiveRegistry, applicationContext);
this.requestMappingResolvers = registrar.getResolvers(); this.requestMappingResolvers = registrar.getResolvers();
registrar = ResolverRegistrar.customResolvers(customResolvers).basic(); registrar = ArgumentResolverRegistrar.configurer(argumentResolverConfigurer).basic();
addResolversTo(registrar, reactiveRegistry, applicationContext); addResolversTo(registrar, reactiveRegistry, applicationContext);
this.exceptionHandlerResolvers = registrar.getResolvers(); this.exceptionHandlerResolvers = registrar.getResolvers();
initControllerAdviceCaches(applicationContext); initControllerAdviceCaches(applicationContext);
} }
private void addResolversTo(ResolverRegistrar registrar, private void addResolversTo(ArgumentResolverRegistrar registrar,
ReactiveAdapterRegistry reactiveRegistry, ConfigurableApplicationContext context) { ReactiveAdapterRegistry reactiveRegistry, ConfigurableApplicationContext context) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
@ -319,7 +322,7 @@ class ControllerMethodResolver {
(AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null); (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null);
private static class ResolverRegistrar { private static class ArgumentResolverRegistrar {
private final List<HandlerMethodArgumentResolver> customResolvers; private final List<HandlerMethodArgumentResolver> customResolvers;
@ -330,10 +333,10 @@ class ControllerMethodResolver {
private final List<HandlerMethodArgumentResolver> result = new ArrayList<>(); private final List<HandlerMethodArgumentResolver> result = new ArrayList<>();
private ResolverRegistrar(List<HandlerMethodArgumentResolver> customResolvers, private ArgumentResolverRegistrar(ArgumentResolverConfigurer configurer,
List<HttpMessageReader<?>> messageReaders, boolean modelAttribute) { List<HttpMessageReader<?>> messageReaders, boolean modelAttribute) {
this.customResolvers = new ArrayList<>(customResolvers); this.customResolvers = configurer.getCustomResolvers();
this.messageReaders = messageReaders != null ? new ArrayList<>(messageReaders) : null; this.messageReaders = messageReaders != null ? new ArrayList<>(messageReaders) : null;
this.modelAttributeSupported = modelAttribute; this.modelAttributeSupported = modelAttribute;
} }
@ -372,32 +375,32 @@ class ControllerMethodResolver {
} }
public static Builder customResolvers(List<HandlerMethodArgumentResolver> customResolvers) { public static Builder configurer(ArgumentResolverConfigurer configurer) {
return new Builder(customResolvers); return new Builder(configurer);
} }
public static class Builder { public static class Builder {
private final List<HandlerMethodArgumentResolver> customResolvers; private final ArgumentResolverConfigurer configurer;
public Builder(List<HandlerMethodArgumentResolver> customResolvers) { public Builder(ArgumentResolverConfigurer configurer) {
this.customResolvers = new ArrayList<>(customResolvers); this.configurer = configurer;
} }
public ResolverRegistrar fullSupport(List<HttpMessageReader<?>> readers) { public ArgumentResolverRegistrar fullSupport(List<HttpMessageReader<?>> readers) {
Assert.notEmpty(readers, "No message readers"); Assert.notEmpty(readers, "No message readers");
return new ResolverRegistrar(this.customResolvers, readers, true); return new ArgumentResolverRegistrar(this.configurer, readers, true);
} }
public ResolverRegistrar modelAttributeSupport() { public ArgumentResolverRegistrar modelAttributeSupport() {
return new ResolverRegistrar(this.customResolvers, null, true); return new ArgumentResolverRegistrar(this.configurer, null, true);
} }
public ResolverRegistrar basic() { public ArgumentResolverRegistrar basic() {
return new ResolverRegistrar(this.customResolvers, null, false); return new ArgumentResolverRegistrar(this.configurer, null, false);
} }
} }

View File

@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -44,7 +43,6 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerAdapter; import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod; import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
@ -63,9 +61,9 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
private WebBindingInitializer webBindingInitializer; private WebBindingInitializer webBindingInitializer;
private ReactiveAdapterRegistry reactiveAdapterRegistry = new ReactiveAdapterRegistry(); private ArgumentResolverConfigurer argumentResolverConfigurer;
private final List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>(8); private ReactiveAdapterRegistry reactiveAdapterRegistry;
private ConfigurableApplicationContext applicationContext; private ConfigurableApplicationContext applicationContext;
@ -117,6 +115,21 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
return this.webBindingInitializer; return this.webBindingInitializer;
} }
/**
* Configure resolvers for controller method arguments.
*/
public void setArgumentResolverConfigurer(ArgumentResolverConfigurer configurer) {
Assert.notNull(configurer, "ArgumentResolverConfigurer is required");
this.argumentResolverConfigurer = configurer;
}
/**
* Return the configured resolvers for controller method arguments.
*/
public ArgumentResolverConfigurer getArgumentResolverConfigurer() {
return this.argumentResolverConfigurer;
}
/** /**
* Configure the registry for adapting various reactive types. * Configure the registry for adapting various reactive types.
* <p>By default this is an instance of {@link ReactiveAdapterRegistry} with * <p>By default this is an instance of {@link ReactiveAdapterRegistry} with
@ -133,21 +146,6 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
return this.reactiveAdapterRegistry; return this.reactiveAdapterRegistry;
} }
/**
* Configure resolvers for custom controller method arguments.
*/
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.customArgumentResolvers.clear();
this.customArgumentResolvers.addAll(resolvers);
}
/**
* Return the configured custom argument resolvers.
*/
public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
return this.customArgumentResolvers;
}
/** /**
* A {@link ConfigurableApplicationContext} is expected for resolving * A {@link ConfigurableApplicationContext} is expected for resolving
* expressions in method argument default values as well as for * expressions in method argument default values as well as for
@ -164,18 +162,22 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
return this.applicationContext; return this.applicationContext;
} }
public ConfigurableBeanFactory getBeanFactory() {
return this.applicationContext.getBeanFactory();
}
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
this.methodResolver = new ControllerMethodResolver(getCustomArgumentResolvers(), if (this.argumentResolverConfigurer == null) {
getMessageReaders(), getReactiveAdapterRegistry(), getApplicationContext()); this.argumentResolverConfigurer = new ArgumentResolverConfigurer();
}
this.modelInitializer = new ModelInitializer(getReactiveAdapterRegistry()); if (this.reactiveAdapterRegistry == null) {
this.reactiveAdapterRegistry = new ReactiveAdapterRegistry();
}
this.methodResolver = new ControllerMethodResolver(this.argumentResolverConfigurer,
this.messageReaders, this.reactiveAdapterRegistry, this.applicationContext);
this.modelInitializer = new ModelInitializer(this.reactiveAdapterRegistry);
} }

View File

@ -100,7 +100,7 @@ public class DelegatingWebFluxConfigurationTests {
verify(webFluxConfigurer).getValidator(); verify(webFluxConfigurer).getValidator();
verify(webFluxConfigurer).getMessageCodesResolver(); verify(webFluxConfigurer).getMessageCodesResolver();
verify(webFluxConfigurer).addFormatters(formatterRegistry.capture()); verify(webFluxConfigurer).addFormatters(formatterRegistry.capture());
verify(webFluxConfigurer).addArgumentResolvers(any()); verify(webFluxConfigurer).configureArgumentResolvers(any());
assertSame(formatterRegistry.getValue(), initializerConversionService); assertSame(formatterRegistry.getValue(), initializerConversionService);
assertEquals(9, codecsConfigurer.getValue().getReaders().size()); assertEquals(9, codecsConfigurer.getValue().getReaders().size());

View File

@ -67,8 +67,9 @@ public class ControllerMethodResolverTests {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
List<HandlerMethodArgumentResolver> customResolvers = ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
Arrays.asList(new CustomArgumentResolver(), new CustomSyncArgumentResolver()); configurer.addCustomResolver(new CustomArgumentResolver());
configurer.addCustomResolver(new CustomSyncArgumentResolver());
List<HttpMessageReader<?>> messageReaders = Arrays.asList( List<HttpMessageReader<?>> messageReaders = Arrays.asList(
new DecoderHttpMessageReader<>(new ByteArrayDecoder()), new DecoderHttpMessageReader<>(new ByteArrayDecoder()),
@ -79,7 +80,7 @@ public class ControllerMethodResolverTests {
applicationContext.refresh(); applicationContext.refresh();
this.methodResolver = new ControllerMethodResolver( this.methodResolver = new ControllerMethodResolver(
customResolvers, messageReaders, new ReactiveAdapterRegistry(), applicationContext); configurer, messageReaders, new ReactiveAdapterRegistry(), applicationContext);
Method method = ResolvableMethod.on(TestController.class).mockCall(TestController::handle).method(); Method method = ResolvableMethod.on(TestController.class).mockCall(TestController::handle).method();
this.handlerMethod = new HandlerMethod(new TestController(), method); this.handlerMethod = new HandlerMethod(new TestController(), method);