diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
index 3d2d8ca29c2..13870f7b844 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java
@@ -20,17 +20,24 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -47,9 +54,14 @@ import static org.springframework.core.MethodIntrospector.selectMethods;
/**
* Package-private class to assist {@link RequestMappingHandlerAdapter} with
- * resolving and caching {@code @InitBinder}, {@code @ModelAttribute}, and
- * {@code @ExceptionHandler} methods declared in the {@code @Controller} or in
- * {@code @ControllerAdvice} components.
+ * resolving, initializing, and caching annotated methods declared in
+ * {@code @Controller} and {@code @ControllerAdvice} components:
+ *
+ * - {@code @InitBinder}
+ *
- {@code @ModelAttribute}
+ *
- {@code @RequestMapping}
+ *
- {@code @ExceptionHandler}
+ *
*
* @author Rossen Stoyanchev
* @since 5.0
@@ -59,37 +71,92 @@ class ControllerMethodResolver {
private static Log logger = LogFactory.getLog(ControllerMethodResolver.class);
- private final List argumentResolvers;
+ private final List initBinderResolvers;
- private final List initBinderArgumentResolvers;
+ private final List modelAttributeResolvers;
+ private final List requestMappingResolvers;
- private final Map, Set> binderMethodCache = new ConcurrentHashMap<>(64);
+ private final List exceptionHandlerResolvers;
- private final Map, Set> attributeMethodCache = new ConcurrentHashMap<>(64);
- private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache =
- new ConcurrentHashMap<>(64);
+ private final Map, Set> initBinderMethodCache = new ConcurrentHashMap<>(64);
+ private final Map, Set> modelAttributeMethodCache = new ConcurrentHashMap<>(64);
- private final Map> binderAdviceCache = new LinkedHashMap<>(64);
+ private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
- private final Map> attributeAdviceCache = new LinkedHashMap<>(64);
+
+ private final Map> initBinderAdviceCache = new LinkedHashMap<>(64);
+
+ private final Map> modelAttributeAdviceCache = new LinkedHashMap<>(64);
private final Map exceptionHandlerAdviceCache =
new LinkedHashMap<>(64);
- ControllerMethodResolver(List argumentResolvers,
- List initBinderArgumentResolvers,
- ApplicationContext applicationContext) {
+ ControllerMethodResolver(List customResolvers,
+ List> messageReaders, ReactiveAdapterRegistry reactiveRegistry,
+ ConfigurableApplicationContext applicationContext) {
+
+ Assert.notNull(customResolvers, "'customResolvers' should not be null");
+ Assert.notNull(reactiveRegistry, "ReactiveAdapterRegistry is required");
+ Assert.notNull(applicationContext, "ConfigurableApplicationContext is required");
+
+ ResolverRegistrar registrar = ResolverRegistrar.customResolvers(customResolvers).basic();
+ addResolversTo(registrar, reactiveRegistry, applicationContext);
+ this.initBinderResolvers = registrar.getSyncResolvers();
- this.argumentResolvers = argumentResolvers;
- this.initBinderArgumentResolvers = initBinderArgumentResolvers;
+ registrar = ResolverRegistrar.customResolvers(customResolvers).modelAttributeSupport();
+ addResolversTo(registrar, reactiveRegistry, applicationContext);
+ this.modelAttributeResolvers = registrar.getResolvers();
+
+ registrar = ResolverRegistrar.customResolvers(customResolvers).fullSupport(messageReaders);
+ addResolversTo(registrar, reactiveRegistry, applicationContext);
+ this.requestMappingResolvers = registrar.getResolvers();
+
+ registrar = ResolverRegistrar.customResolvers(customResolvers).basic();
+ addResolversTo(registrar, reactiveRegistry, applicationContext);
+ this.exceptionHandlerResolvers = registrar.getResolvers();
initControllerAdviceCaches(applicationContext);
}
+ private void addResolversTo(ResolverRegistrar registrar,
+ ReactiveAdapterRegistry reactiveRegistry, ConfigurableApplicationContext context) {
+
+ ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
+
+ // Annotation-based...
+ registrar.add(new RequestParamMethodArgumentResolver(beanFactory, reactiveRegistry, false));
+ registrar.add(new RequestParamMapMethodArgumentResolver(reactiveRegistry));
+ registrar.add(new PathVariableMethodArgumentResolver(beanFactory, reactiveRegistry));
+ registrar.add(new PathVariableMapMethodArgumentResolver(reactiveRegistry));
+ registrar.addIfRequestBody(readers -> new RequestBodyArgumentResolver(readers, reactiveRegistry));
+ registrar.addIfModelAttribute(() -> new ModelAttributeMethodArgumentResolver(reactiveRegistry, false));
+ registrar.add(new RequestHeaderMethodArgumentResolver(beanFactory, reactiveRegistry));
+ registrar.add(new RequestHeaderMapMethodArgumentResolver(reactiveRegistry));
+ registrar.add(new CookieValueMethodArgumentResolver(beanFactory, reactiveRegistry));
+ registrar.add(new ExpressionValueMethodArgumentResolver(beanFactory, reactiveRegistry));
+ registrar.add(new SessionAttributeMethodArgumentResolver(beanFactory, reactiveRegistry));
+ registrar.add(new RequestAttributeMethodArgumentResolver(beanFactory, reactiveRegistry));
+
+ // Type-based...
+ registrar.addIfRequestBody(readers -> new HttpEntityArgumentResolver(readers, reactiveRegistry));
+ registrar.add(new ModelArgumentResolver(reactiveRegistry));
+ registrar.addIfModelAttribute(() -> new ErrorsMethodArgumentResolver(reactiveRegistry));
+ registrar.add(new ServerWebExchangeArgumentResolver(reactiveRegistry));
+ registrar.add(new PrincipalArgumentResolver(reactiveRegistry));
+ registrar.add(new WebSessionArgumentResolver(reactiveRegistry));
+
+ // Custom...
+ registrar.addCustomResolvers();
+
+ // Catch-all...
+ registrar.add(new RequestParamMethodArgumentResolver(beanFactory, reactiveRegistry, true));
+ registrar.addIfModelAttribute(() -> new ModelAttributeMethodArgumentResolver(reactiveRegistry, true));
+ }
+
private void initControllerAdviceCaches(ApplicationContext applicationContext) {
if (applicationContext == null) {
return;
@@ -105,14 +172,14 @@ class ControllerMethodResolver {
Class> beanType = bean.getBeanType();
Set attrMethods = selectMethods(beanType, ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
- this.attributeAdviceCache.put(bean, attrMethods);
+ this.modelAttributeAdviceCache.put(bean, attrMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + bean);
}
}
Set binderMethods = selectMethods(beanType, BINDER_METHODS);
if (!binderMethods.isEmpty()) {
- this.binderAdviceCache.put(bean, binderMethods);
+ this.initBinderAdviceCache.put(bean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + bean);
}
@@ -129,101 +196,116 @@ class ControllerMethodResolver {
/**
- * Find {@code @InitBinder} methods from {@code @ControllerAdvice}
- * components or from the same controller as the given request handling method.
+ * Return an {@link InvocableHandlerMethod} for the given
+ * {@code @RequestMapping} method initialized with argument resolvers.
*/
- public List resolveInitBinderMethods(HandlerMethod handlerMethod) {
+ public InvocableHandlerMethod getRequestMappingMethod(HandlerMethod handlerMethod) {
+ InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
+ invocable.setArgumentResolvers(this.requestMappingResolvers);
+ return invocable;
+ }
+
+ /**
+ * Find {@code @InitBinder} methods in {@code @ControllerAdvice} components
+ * or in the controller of the given {@code @RequestMapping} method.
+ */
+ public List getInitBinderMethods(HandlerMethod handlerMethod) {
List result = new ArrayList<>();
Class> handlerType = handlerMethod.getBeanType();
// Global methods first
- this.binderAdviceCache.entrySet().forEach(entry -> {
+ this.initBinderAdviceCache.entrySet().forEach(entry -> {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
- entry.getValue().forEach(method -> result.add(createBinderMethod(bean, method)));
+ entry.getValue().forEach(method -> result.add(getInitBinderMethod(bean, method)));
}
});
- this.binderMethodCache
+ this.initBinderMethodCache
.computeIfAbsent(handlerType, aClass -> selectMethods(handlerType, BINDER_METHODS))
.forEach(method -> {
Object bean = handlerMethod.getBean();
- result.add(createBinderMethod(bean, method));
+ result.add(getInitBinderMethod(bean, method));
});
return result;
}
- private SyncInvocableHandlerMethod createBinderMethod(Object bean, Method method) {
+ private SyncInvocableHandlerMethod getInitBinderMethod(Object bean, Method method) {
SyncInvocableHandlerMethod invocable = new SyncInvocableHandlerMethod(bean, method);
- invocable.setArgumentResolvers(this.initBinderArgumentResolvers);
+ invocable.setArgumentResolvers(this.initBinderResolvers);
return invocable;
}
/**
- * Find {@code @ModelAttribute} methods from {@code @ControllerAdvice}
- * components or from the same controller as the given request handling method.
+ * Find {@code @ModelAttribute} methods in {@code @ControllerAdvice}
+ * components or in the controller of the given {@code @RequestMapping} method.
*/
- public List resolveModelAttributeMethods(HandlerMethod handlerMethod) {
+ public List getModelAttributeMethods(HandlerMethod handlerMethod) {
List result = new ArrayList<>();
Class> handlerType = handlerMethod.getBeanType();
// Global methods first
- this.attributeAdviceCache.entrySet().forEach(entry -> {
+ this.modelAttributeAdviceCache.entrySet().forEach(entry -> {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
- entry.getValue().forEach(method -> result.add(createHandlerMethod(bean, method)));
+ entry.getValue().forEach(method -> result.add(createAttributeMethod(bean, method)));
}
});
- this.attributeMethodCache
+ this.modelAttributeMethodCache
.computeIfAbsent(handlerType, aClass -> selectMethods(handlerType, ATTRIBUTE_METHODS))
.forEach(method -> {
Object bean = handlerMethod.getBean();
- result.add(createHandlerMethod(bean, method));
+ result.add(createAttributeMethod(bean, method));
});
return result;
}
- private InvocableHandlerMethod createHandlerMethod(Object bean, Method method) {
+ private InvocableHandlerMethod createAttributeMethod(Object bean, Method method) {
InvocableHandlerMethod invocable = new InvocableHandlerMethod(bean, method);
- invocable.setArgumentResolvers(this.argumentResolvers);
+ invocable.setArgumentResolvers(this.modelAttributeResolvers);
return invocable;
}
/**
- * Find a matching {@code @ExceptionHandler} method from
- * {@code @ControllerAdvice} components or from the same controller as the
- * given request handling method.
+ * Find an {@code @ExceptionHandler} method in {@code @ControllerAdvice}
+ * components or in the controller of the given {@code @RequestMapping} method.
*/
- public InvocableHandlerMethod resolveExceptionHandlerMethod(Throwable ex, HandlerMethod handlerMethod) {
+ public Optional getExceptionHandlerMethod(Throwable ex,
+ HandlerMethod handlerMethod) {
Class> handlerType = handlerMethod.getBeanType();
- ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache
- .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new);
-
- return Optional
- .ofNullable(resolver.resolveMethodByThrowable(ex))
- .map(method -> createHandlerMethod(handlerMethod.getBean(), method))
- .orElseGet(() ->
- this.exceptionHandlerAdviceCache.entrySet().stream()
- .map(entry -> {
- if (entry.getKey().isApplicableToBeanType(handlerType)) {
- Method method = entry.getValue().resolveMethodByThrowable(ex);
- if (method != null) {
- Object bean = entry.getKey().resolveBean();
- return createHandlerMethod(bean, method);
- }
- }
- return null;
- })
- .filter(Objects::nonNull)
- .findFirst()
- .orElse(null));
+ // Controller-local first...
+ Object targetBean = handlerMethod.getBean();
+ Method targetMethod = this.exceptionHandlerCache
+ .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
+ .resolveMethodByThrowable(ex);
+
+ if (targetMethod == null) {
+ // Global exception handlers...
+ for (ControllerAdviceBean advice : this.exceptionHandlerAdviceCache.keySet()) {
+ if (advice.isApplicableToBeanType(handlerType)) {
+ targetBean = advice.resolveBean();
+ targetMethod = this.exceptionHandlerAdviceCache.get(advice).resolveMethodByThrowable(ex);
+ if (targetMethod != null) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (targetMethod == null) {
+ return Optional.empty();
+ }
+
+ InvocableHandlerMethod invocable = new InvocableHandlerMethod(targetBean, targetMethod);
+ invocable.setArgumentResolvers(this.exceptionHandlerResolvers);
+ return Optional.of(invocable);
}
@@ -236,4 +318,89 @@ class ControllerMethodResolver {
(AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&
(AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null);
+
+ private static class ResolverRegistrar {
+
+ private final List customResolvers;
+
+ private final List> messageReaders;
+
+ private final boolean modelAttributeSupported;
+
+ private final List result = new ArrayList<>();
+
+
+ private ResolverRegistrar(List customResolvers,
+ List> messageReaders, boolean modelAttribute) {
+
+ this.customResolvers = new ArrayList<>(customResolvers);
+ this.messageReaders = messageReaders != null ? new ArrayList<>(messageReaders) : null;
+ this.modelAttributeSupported = modelAttribute;
+ }
+
+
+ public void add(HandlerMethodArgumentResolver resolver) {
+ this.result.add(resolver);
+ }
+
+ public void addIfRequestBody(Function>, HandlerMethodArgumentResolver> function) {
+ if (this.messageReaders != null) {
+ add(function.apply(this.messageReaders));
+ }
+ }
+
+ public void addIfModelAttribute(Supplier supplier) {
+ if (this.modelAttributeSupported) {
+ add(supplier.get());
+ }
+ }
+
+ public void addCustomResolvers() {
+ this.customResolvers.forEach(this::add);
+ }
+
+
+ public List getResolvers() {
+ return this.result;
+ }
+
+ public List getSyncResolvers() {
+ return this.result.stream()
+ .filter(resolver -> resolver instanceof SyncHandlerMethodArgumentResolver)
+ .map(resolver -> (SyncHandlerMethodArgumentResolver) resolver)
+ .collect(Collectors.toList());
+ }
+
+
+ public static Builder customResolvers(List customResolvers) {
+ return new Builder(customResolvers);
+ }
+
+
+ public static class Builder {
+
+ private final List customResolvers;
+
+
+ public Builder(List customResolvers) {
+ this.customResolvers = new ArrayList<>(customResolvers);
+ }
+
+
+ public ResolverRegistrar fullSupport(List> readers) {
+ Assert.notEmpty(readers, "No message readers");
+ return new ResolverRegistrar(this.customResolvers, readers, true);
+ }
+
+ public ResolverRegistrar modelAttributeSupport() {
+ return new ResolverRegistrar(this.customResolvers, null, true);
+ }
+
+ public ResolverRegistrar basic() {
+ return new ResolverRegistrar(this.customResolvers, null, false);
+ }
+ }
+
+ }
+
}
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
index 89031a3c044..7b8a7efd2a2 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerAdapter.java
@@ -46,8 +46,6 @@ import org.springframework.web.reactive.HandlerAdapter;
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.SyncHandlerMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -61,28 +59,21 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
- private final List> messageReaders = new ArrayList<>(10);
+ private final List> messageReaders = new ArrayList<>(32);
private WebBindingInitializer webBindingInitializer;
private ReactiveAdapterRegistry reactiveAdapterRegistry = new ReactiveAdapterRegistry();
- private List customArgumentResolvers;
-
- private List argumentResolvers;
-
- private List customInitBinderArgumentResolvers;
-
- private List initBinderArgumentResolvers;
+ private final List customArgumentResolvers = new ArrayList<>(8);
private ConfigurableApplicationContext applicationContext;
- private ControllerMethodResolver controllerMethodResolver;
+ private ControllerMethodResolver methodResolver;
private ModelInitializer modelInitializer;
-
public RequestMappingHandlerAdapter() {
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
@@ -143,66 +134,20 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
}
/**
- * Configure custom argument resolvers without overriding the built-in ones.
+ * Configure resolvers for custom controller method arguments.
*/
public void setCustomArgumentResolvers(List resolvers) {
- this.customArgumentResolvers = resolvers;
+ this.customArgumentResolvers.clear();
+ this.customArgumentResolvers.addAll(resolvers);
}
/**
- * Return the custom argument resolvers.
+ * Return the configured custom argument resolvers.
*/
public List getCustomArgumentResolvers() {
return this.customArgumentResolvers;
}
- /**
- * Configure the complete list of supported argument types thus overriding
- * the resolvers that would otherwise be configured by default.
- */
- public void setArgumentResolvers(List resolvers) {
- this.argumentResolvers = new ArrayList<>(resolvers);
- }
-
- /**
- * Return the configured argument resolvers.
- */
- public List getArgumentResolvers() {
- return this.argumentResolvers;
- }
-
- /**
- * Configure custom argument resolvers for {@code @InitBinder} methods.
- */
- public void setCustomInitBinderArgumentResolvers(List resolvers) {
- this.customInitBinderArgumentResolvers = resolvers;
- }
-
- /**
- * Return the custom {@code @InitBinder} argument resolvers.
- */
- public List getCustomInitBinderArgumentResolvers() {
- return this.customInitBinderArgumentResolvers;
- }
-
- /**
- * Configure the supported argument types in {@code @InitBinder} methods.
- */
- public void setInitBinderArgumentResolvers(List resolvers) {
- this.initBinderArgumentResolvers = null;
- if (resolvers != null) {
- this.initBinderArgumentResolvers = new ArrayList<>();
- this.initBinderArgumentResolvers.addAll(resolvers);
- }
- }
-
- /**
- * Return the configured argument resolvers for {@code @InitBinder} methods.
- */
- public List getInitBinderArgumentResolvers() {
- return this.initBinderArgumentResolvers;
- }
-
/**
* A {@link ConfigurableApplicationContext} is expected for resolving
* expressions in method argument default values as well as for
@@ -227,83 +172,12 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
@Override
public void afterPropertiesSet() throws Exception {
- if (this.argumentResolvers == null) {
- this.argumentResolvers = getDefaultArgumentResolvers();
- }
- if (this.initBinderArgumentResolvers == null) {
- this.initBinderArgumentResolvers = getDefaultInitBinderArgumentResolvers();
- }
-
- this.controllerMethodResolver = new ControllerMethodResolver(
- getArgumentResolvers(), getInitBinderArgumentResolvers(), getApplicationContext());
+ this.methodResolver = new ControllerMethodResolver(getCustomArgumentResolvers(),
+ getMessageReaders(), getReactiveAdapterRegistry(), getApplicationContext());
this.modelInitializer = new ModelInitializer(getReactiveAdapterRegistry());
}
- protected List getDefaultArgumentResolvers() {
- List resolvers = new ArrayList<>();
-
- // Annotation-based argument resolution
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry(), false));
- resolvers.add(new RequestParamMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new PathVariableMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new PathVariableMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new RequestBodyArgumentResolver(getMessageReaders(), getReactiveAdapterRegistry()));
- resolvers.add(new ModelAttributeMethodArgumentResolver(getReactiveAdapterRegistry(), false));
- resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new RequestHeaderMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new CookieValueMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new SessionAttributeMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new RequestAttributeMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
-
- // Type-based argument resolution
- resolvers.add(new HttpEntityArgumentResolver(getMessageReaders(), getReactiveAdapterRegistry()));
- resolvers.add(new ModelArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new ErrorsMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new ServerWebExchangeArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new PrincipalArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new WebSessionArgumentResolver(getReactiveAdapterRegistry()));
-
- // Custom resolvers
- if (getCustomArgumentResolvers() != null) {
- resolvers.addAll(getCustomArgumentResolvers());
- }
-
- // Catch-all
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry(), true));
- resolvers.add(new ModelAttributeMethodArgumentResolver(getReactiveAdapterRegistry(), true));
- return resolvers;
- }
-
- protected List getDefaultInitBinderArgumentResolvers() {
- List resolvers = new ArrayList<>();
-
- // Annotation-based argument resolution
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry(), false));
- resolvers.add(new RequestParamMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new PathVariableMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new PathVariableMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new RequestHeaderMapMethodArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new CookieValueMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
- resolvers.add(new RequestAttributeMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry()));
-
- // Type-based argument resolution
- resolvers.add(new ModelArgumentResolver(getReactiveAdapterRegistry()));
- resolvers.add(new ServerWebExchangeArgumentResolver(getReactiveAdapterRegistry()));
-
- // Custom resolvers
- if (getCustomInitBinderArgumentResolvers() != null) {
- resolvers.addAll(getCustomInitBinderArgumentResolvers());
- }
-
- // Catch-all
- resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), getReactiveAdapterRegistry(), true));
- return resolvers;
- }
-
@Override
public boolean supports(Object handler) {
@@ -317,53 +191,43 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
HandlerMethod handlerMethod = (HandlerMethod) handler;
BindingContext bindingContext = new InitBinderBindingContext(
- getWebBindingInitializer(), getInitBinderMethods(handlerMethod));
+ getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod));
- return this.modelInitializer
- .initModel(bindingContext, getModelAttributeMethods(handlerMethod), exchange)
- .then(() -> {
- Function> exceptionHandler =
- ex -> handleException(exchange, handlerMethod, bindingContext, ex);
-
- InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
- invocable.setArgumentResolvers(getArgumentResolvers());
-
- return invocable.invoke(exchange, bindingContext)
- .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
- .otherwise(exceptionHandler);
- });
- }
+ List modelAttributeMethods =
+ this.methodResolver.getModelAttributeMethods(handlerMethod);
- private List getInitBinderMethods(HandlerMethod handlerMethod) {
- return this.controllerMethodResolver.resolveInitBinderMethods(handlerMethod);
- }
-
- private List getModelAttributeMethods(HandlerMethod handlerMethod) {
- return this.controllerMethodResolver.resolveModelAttributeMethods(handlerMethod);
- }
+ Function> exceptionHandler =
+ ex -> handleException(ex, handlerMethod, bindingContext, exchange);
- private Mono handleException(ServerWebExchange exchange, HandlerMethod handlerMethod,
- BindingContext bindingContext, Throwable ex) {
-
- InvocableHandlerMethod invocable =
- this.controllerMethodResolver.resolveExceptionHandlerMethod(ex, handlerMethod);
-
- if (invocable != null) {
- try {
- if (logger.isDebugEnabled()) {
- logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod());
- }
- bindingContext.getModel().asMap().clear();
- Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
- return invocable.invoke(exchange, bindingContext, cause, handlerMethod);
- }
- catch (Throwable invocationEx) {
- if (logger.isWarnEnabled()) {
- logger.warn("Failed to invoke: " + invocable.getMethod(), invocationEx);
- }
- }
- }
- return Mono.error(ex);
+ return this.modelInitializer
+ .initModel(bindingContext, modelAttributeMethods, exchange)
+ .then(() -> this.methodResolver.getRequestMappingMethod(handlerMethod)
+ .invoke(exchange, bindingContext)
+ .doOnNext(result -> result.setExceptionHandler(exceptionHandler))
+ .otherwise(exceptionHandler));
+ }
+
+ private Mono handleException(Throwable ex, HandlerMethod handlerMethod,
+ BindingContext bindingContext, ServerWebExchange exchange) {
+
+ return this.methodResolver.getExceptionHandlerMethod(ex, handlerMethod)
+ .map(invocable -> {
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod());
+ }
+ bindingContext.getModel().asMap().clear();
+ Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
+ return invocable.invoke(exchange, bindingContext, cause, handlerMethod);
+ }
+ catch (Throwable invocationEx) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Failed to invoke: " + invocable.getMethod(), invocationEx);
+ }
+ return null;
+ }
+ })
+ .orElseGet(() -> Mono.error(ex));
}
}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java
new file mode 100644
index 00000000000..88805790eba
--- /dev/null
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolverTests.java
@@ -0,0 +1,295 @@
+/*
+ * 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.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.core.codec.ByteArrayDecoder;
+import org.springframework.core.codec.ByteBufferDecoder;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.codec.DecoderHttpMessageReader;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.method.ResolvableMethod;
+import org.springframework.web.reactive.BindingContext;
+import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
+import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
+import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
+import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Unit tests for {@link ControllerMethodResolver}.
+ * @author Rossen Stoyanchev
+ */
+public class ControllerMethodResolverTests {
+
+ private ControllerMethodResolver methodResolver;
+
+ private HandlerMethod handlerMethod;
+
+
+ @Before
+ public void setUp() throws Exception {
+
+ List customResolvers =
+ Arrays.asList(new CustomArgumentResolver(), new CustomSyncArgumentResolver());
+
+ List> messageReaders = Arrays.asList(
+ new DecoderHttpMessageReader<>(new ByteArrayDecoder()),
+ new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
+
+ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
+ applicationContext.registerBean(TestControllerAdvice.class);
+ applicationContext.refresh();
+
+ this.methodResolver = new ControllerMethodResolver(
+ customResolvers, messageReaders, new ReactiveAdapterRegistry(), applicationContext);
+
+ Method method = ResolvableMethod.on(TestController.class).mockCall(TestController::handle).method();
+ this.handlerMethod = new HandlerMethod(new TestController(), method);
+ }
+
+
+ @Test
+ public void requestMappingArgumentResolvers() throws Exception {
+
+ InvocableHandlerMethod invocable = this.methodResolver.getRequestMappingMethod(this.handlerMethod);
+ List resolvers = invocable.getResolvers();
+
+ AtomicInteger index = new AtomicInteger(-1);
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestBodyArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(HttpEntityArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ErrorsMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ }
+
+ @Test
+ public void modelAttributeArgumentResolvers() throws Exception {
+
+ List methods =
+ this.methodResolver.getModelAttributeMethods(this.handlerMethod);
+
+ assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size());
+ InvocableHandlerMethod invocable = methods.get(0);
+ List resolvers = invocable.getResolvers();
+
+ AtomicInteger index = new AtomicInteger(-1);
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ErrorsMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ModelAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ }
+
+ @Test
+ public void initBinderArgumentResolvers() throws Exception {
+
+ List methods =
+ this.methodResolver.getInitBinderMethods(this.handlerMethod);
+
+ assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size());
+ SyncInvocableHandlerMethod invocable = methods.get(0);
+ List resolvers = invocable.getResolvers();
+
+ AtomicInteger index = new AtomicInteger(-1);
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ }
+
+ @Test
+ public void exceptionHandlerArgumentResolvers() throws Exception {
+
+ Optional optional =
+ this.methodResolver.getExceptionHandlerMethod(
+ new ResponseStatusException(HttpStatus.BAD_REQUEST, "reason"), this.handlerMethod);
+
+ InvocableHandlerMethod invocable = optional.orElseThrow(() -> new AssertionError("No match"));
+ assertEquals(TestController.class, invocable.getBeanType());
+ List resolvers = invocable.getResolvers();
+
+ AtomicInteger index = new AtomicInteger(-1);
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestParamMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PathVariableMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestHeaderMapMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CookieValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ExpressionValueMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(SessionAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(RequestAttributeMethodArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(ModelArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(ServerWebExchangeArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(PrincipalArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(WebSessionArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(CustomArgumentResolver.class, next(resolvers, index).getClass());
+ assertEquals(CustomSyncArgumentResolver.class, next(resolvers, index).getClass());
+
+ assertEquals(RequestParamMethodArgumentResolver.class, next(resolvers, index).getClass());
+ }
+
+ @Test
+ public void exceptionHandlerFromControllerAdvice() throws Exception {
+
+ Optional optional =
+ this.methodResolver.getExceptionHandlerMethod(
+ new IllegalStateException("reason"), this.handlerMethod);
+
+ InvocableHandlerMethod invocable = optional.orElseThrow(() -> new AssertionError("No match"));
+ assertNotNull(invocable);
+ assertEquals(TestControllerAdvice.class, invocable.getBeanType());
+ }
+
+
+ private static HandlerMethodArgumentResolver next(
+ List extends HandlerMethodArgumentResolver> resolvers, AtomicInteger index) {
+
+ return resolvers.get(index.incrementAndGet());
+ }
+
+
+ @Controller
+ private static class TestController {
+
+ @InitBinder
+ void initDataBinder() {}
+
+ @ModelAttribute
+ void initModel() {}
+
+ @GetMapping
+ void handle() {}
+
+ @ExceptionHandler
+ void handleException(ResponseStatusException ex) {}
+
+ }
+
+ @ControllerAdvice
+ private static class TestControllerAdvice {
+
+ @InitBinder
+ void initDataBinder() {}
+
+ @ModelAttribute
+ void initModel() {}
+
+ @ExceptionHandler
+ void handleException(IllegalStateException ex) {}
+
+ }
+
+ private static class CustomArgumentResolver implements HandlerMethodArgumentResolver {
+
+ @Override
+ public boolean supportsParameter(MethodParameter p) {
+ return false;
+ }
+
+ @Override
+ public Mono