提交 c5bcefbd 编写于 作者: R Rossen Stoyanchev

Complete RequestMappingHandlerAdapter refactoring

ControllerMethodResolver now also encapsulates initialization, storage,
and use of HandlerMethodArgumentResolver's by annotated method type.
上级 b0533113
......@@ -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:
* <ul>
* <li>{@code @InitBinder}
* <li>{@code @ModelAttribute}
* <li>{@code @RequestMapping}
* <li>{@code @ExceptionHandler}
* </ul>
*
* @author Rossen Stoyanchev
* @since 5.0
......@@ -59,37 +71,92 @@ class ControllerMethodResolver {
private static Log logger = LogFactory.getLog(ControllerMethodResolver.class);
private final List<HandlerMethodArgumentResolver> argumentResolvers;
private final List<SyncHandlerMethodArgumentResolver> initBinderResolvers;
private final List<SyncHandlerMethodArgumentResolver> initBinderArgumentResolvers;
private final List<HandlerMethodArgumentResolver> modelAttributeResolvers;
private final List<HandlerMethodArgumentResolver> requestMappingResolvers;
private final Map<Class<?>, Set<Method>> binderMethodCache = new ConcurrentHashMap<>(64);
private final List<HandlerMethodArgumentResolver> exceptionHandlerResolvers;
private final Map<Class<?>, Set<Method>> attributeMethodCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<>(64);
private final Map<Class<?>, Set<Method>> initBinderMethodCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, Set<Method>> modelAttributeMethodCache = new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> binderAdviceCache = new LinkedHashMap<>(64);
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> attributeAdviceCache = new LinkedHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>(64);
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
new LinkedHashMap<>(64);
ControllerMethodResolver(List<HandlerMethodArgumentResolver> argumentResolvers,
List<SyncHandlerMethodArgumentResolver> initBinderArgumentResolvers,
ApplicationContext applicationContext) {
ControllerMethodResolver(List<HandlerMethodArgumentResolver> customResolvers,
List<HttpMessageReader<?>> 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<Method> 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<Method> 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<SyncInvocableHandlerMethod> 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<SyncInvocableHandlerMethod> getInitBinderMethods(HandlerMethod handlerMethod) {
List<SyncInvocableHandlerMethod> 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<InvocableHandlerMethod> resolveModelAttributeMethods(HandlerMethod handlerMethod) {
public List<InvocableHandlerMethod> getModelAttributeMethods(HandlerMethod handlerMethod) {
List<InvocableHandlerMethod> 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<InvocableHandlerMethod> 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<HandlerMethodArgumentResolver> customResolvers;
private final List<HttpMessageReader<?>> messageReaders;
private final boolean modelAttributeSupported;
private final List<HandlerMethodArgumentResolver> result = new ArrayList<>();
private ResolverRegistrar(List<HandlerMethodArgumentResolver> customResolvers,
List<HttpMessageReader<?>> 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<List<HttpMessageReader<?>>, HandlerMethodArgumentResolver> function) {
if (this.messageReaders != null) {
add(function.apply(this.messageReaders));
}
}
public void addIfModelAttribute(Supplier<HandlerMethodArgumentResolver> supplier) {
if (this.modelAttributeSupported) {
add(supplier.get());
}
}
public void addCustomResolvers() {
this.customResolvers.forEach(this::add);
}
public List<HandlerMethodArgumentResolver> getResolvers() {
return this.result;
}
public List<SyncHandlerMethodArgumentResolver> getSyncResolvers() {
return this.result.stream()
.filter(resolver -> resolver instanceof SyncHandlerMethodArgumentResolver)
.map(resolver -> (SyncHandlerMethodArgumentResolver) resolver)
.collect(Collectors.toList());
}
public static Builder customResolvers(List<HandlerMethodArgumentResolver> customResolvers) {
return new Builder(customResolvers);
}
public static class Builder {
private final List<HandlerMethodArgumentResolver> customResolvers;
public Builder(List<HandlerMethodArgumentResolver> customResolvers) {
this.customResolvers = new ArrayList<>(customResolvers);
}
public ResolverRegistrar fullSupport(List<HttpMessageReader<?>> 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);
}
}
}
}
......@@ -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<HttpMessageReader<?>> messageReaders = new ArrayList<>(10);
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>(32);
private WebBindingInitializer webBindingInitializer;
private ReactiveAdapterRegistry reactiveAdapterRegistry = new ReactiveAdapterRegistry();
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private List<HandlerMethodArgumentResolver> argumentResolvers;
private List<SyncHandlerMethodArgumentResolver> customInitBinderArgumentResolvers;
private List<SyncHandlerMethodArgumentResolver> initBinderArgumentResolvers;
private final List<HandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> resolvers) {
this.customArgumentResolvers = resolvers;
this.customArgumentResolvers.clear();
this.customArgumentResolvers.addAll(resolvers);
}
/**
* Return the custom argument resolvers.
* Return the configured custom argument resolvers.
*/
public List<HandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> resolvers) {
this.argumentResolvers = new ArrayList<>(resolvers);
}
/**
* Return the configured argument resolvers.
*/
public List<HandlerMethodArgumentResolver> getArgumentResolvers() {
return this.argumentResolvers;
}
/**
* Configure custom argument resolvers for {@code @InitBinder} methods.
*/
public void setCustomInitBinderArgumentResolvers(List<SyncHandlerMethodArgumentResolver> resolvers) {
this.customInitBinderArgumentResolvers = resolvers;
}
/**
* Return the custom {@code @InitBinder} argument resolvers.
*/
public List<SyncHandlerMethodArgumentResolver> getCustomInitBinderArgumentResolvers() {
return this.customInitBinderArgumentResolvers;
}
/**
* Configure the supported argument types in {@code @InitBinder} methods.
*/
public void setInitBinderArgumentResolvers(List<SyncHandlerMethodArgumentResolver> 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<SyncHandlerMethodArgumentResolver> 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<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> 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<SyncHandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
List<SyncHandlerMethodArgumentResolver> 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<Throwable, Mono<HandlerResult>> 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<InvocableHandlerMethod> modelAttributeMethods =
this.methodResolver.getModelAttributeMethods(handlerMethod);
private List<SyncInvocableHandlerMethod> getInitBinderMethods(HandlerMethod handlerMethod) {
return this.controllerMethodResolver.resolveInitBinderMethods(handlerMethod);
}
private List<InvocableHandlerMethod> getModelAttributeMethods(HandlerMethod handlerMethod) {
return this.controllerMethodResolver.resolveModelAttributeMethods(handlerMethod);
}
Function<Throwable, Mono<HandlerResult>> exceptionHandler =
ex -> handleException(ex, handlerMethod, bindingContext, exchange);
private Mono<HandlerResult> 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<HandlerResult> 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));
}
}
/*
* 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<HandlerMethodArgumentResolver> customResolvers =
Arrays.asList(new CustomArgumentResolver(), new CustomSyncArgumentResolver());
List<HttpMessageReader<?>> 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<HandlerMethodArgumentResolver> 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<InvocableHandlerMethod> methods =
this.methodResolver.getModelAttributeMethods(this.handlerMethod);
assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size());
InvocableHandlerMethod invocable = methods.get(0);
List<HandlerMethodArgumentResolver> 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<SyncInvocableHandlerMethod> methods =
this.methodResolver.getInitBinderMethods(this.handlerMethod);
assertEquals("Expected one each from Controller + ControllerAdvice", 2, methods.size());
SyncInvocableHandlerMethod invocable = methods.get(0);
List<SyncHandlerMethodArgumentResolver> 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<InvocableHandlerMethod> 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<HandlerMethodArgumentResolver> 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<InvocableHandlerMethod> 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<Object> resolveArgument(MethodParameter p, BindingContext c, ServerWebExchange e) {
return null;
}
}
private static class CustomSyncArgumentResolver extends CustomArgumentResolver
implements SyncHandlerMethodArgumentResolver {
@Override
public Optional<Object> resolveArgumentValue(MethodParameter p, BindingContext c, ServerWebExchange e) {
return null;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册