Complete RequestMappingHandlerAdapter refactoring
ControllerMethodResolver now also encapsulates initialization, storage, and use of HandlerMethodArgumentResolver's by annotated method type.
This commit is contained in:
parent
b053311306
commit
c5bcefbd07
|
|
@ -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 List<HandlerMethodArgumentResolver> exceptionHandlerResolvers;
|
||||
|
||||
|
||||
private final Map<Class<?>, Set<Method>> binderMethodCache = new ConcurrentHashMap<>(64);
|
||||
private final Map<Class<?>, Set<Method>> initBinderMethodCache = new ConcurrentHashMap<>(64);
|
||||
|
||||
private final Map<Class<?>, Set<Method>> attributeMethodCache = new ConcurrentHashMap<>(64);
|
||||
private final Map<Class<?>, Set<Method>> modelAttributeMethodCache = new ConcurrentHashMap<>(64);
|
||||
|
||||
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||
new ConcurrentHashMap<>(64);
|
||||
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
|
||||
|
||||
|
||||
private final Map<ControllerAdviceBean, Set<Method>> binderAdviceCache = new LinkedHashMap<>(64);
|
||||
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>(64);
|
||||
|
||||
private final Map<ControllerAdviceBean, Set<Method>> attributeAdviceCache = 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) {
|
||||
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
this.initBinderArgumentResolvers = initBinderArgumentResolvers;
|
||||
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();
|
||||
|
||||
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);
|
||||
// Controller-local first...
|
||||
Object targetBean = handlerMethod.getBean();
|
||||
Method targetMethod = this.exceptionHandlerCache
|
||||
.computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
|
||||
.resolveMethodByThrowable(ex);
|
||||
|
||||
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));
|
||||
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));
|
||||
|
||||
List<InvocableHandlerMethod> modelAttributeMethods =
|
||||
this.methodResolver.getModelAttributeMethods(handlerMethod);
|
||||
|
||||
Function<Throwable, Mono<HandlerResult>> exceptionHandler =
|
||||
ex -> handleException(ex, handlerMethod, bindingContext, exchange);
|
||||
|
||||
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);
|
||||
});
|
||||
.initModel(bindingContext, modelAttributeMethods, exchange)
|
||||
.then(() -> this.methodResolver.getRequestMappingMethod(handlerMethod)
|
||||
.invoke(exchange, bindingContext)
|
||||
.doOnNext(result -> result.setExceptionHandler(exceptionHandler))
|
||||
.otherwise(exceptionHandler));
|
||||
}
|
||||
|
||||
private List<SyncInvocableHandlerMethod> getInitBinderMethods(HandlerMethod handlerMethod) {
|
||||
return this.controllerMethodResolver.resolveInitBinderMethods(handlerMethod);
|
||||
}
|
||||
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
|
||||
BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
|
||||
private List<InvocableHandlerMethod> getModelAttributeMethods(HandlerMethod handlerMethod) {
|
||||
return this.controllerMethodResolver.resolveModelAttributeMethods(handlerMethod);
|
||||
}
|
||||
|
||||
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.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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue