Polish ExceptionHandler initialization

In preparation for SPR-15132
This commit is contained in:
Rossen Stoyanchev 2017-03-01 15:45:50 -05:00
parent d8b150e83d
commit 24034447f6
2 changed files with 38 additions and 41 deletions

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -88,9 +89,9 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
private ModelInitializer modelInitializer; private ModelInitializer modelInitializer;
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64); private final Map<Class<?>, Set<Method>> binderMethodCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap<>(64); private final Map<Class<?>, Set<Method>> attributeMethodCache = new ConcurrentHashMap<>(64);
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<>(64); new ConcurrentHashMap<>(64);
@ -305,29 +306,33 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) { public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler; HandlerMethod handlerMethod = (HandlerMethod) handler;
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
invocable.setArgumentResolvers(getArgumentResolvers());
BindingContext bindingContext = new InitBinderBindingContext( BindingContext bindingContext = new InitBinderBindingContext(
getWebBindingInitializer(), getInitBinderMethods(handlerMethod)); getWebBindingInitializer(), getBinderMethods(handlerMethod));
Mono<Void> modelCompletion = this.modelInitializer.initModel( Mono<Void> modelCompletion = this.modelInitializer.initModel(
bindingContext, getAttributeMethods(handlerMethod), exchange); bindingContext, getAttributeMethods(handlerMethod), exchange);
return modelCompletion.then(() -> Function<Throwable, Mono<HandlerResult>> exceptionHandler =
invocable.invoke(exchange, bindingContext) ex -> handleException(ex, handlerMethod, bindingContext, exchange);
.doOnNext(result -> result.setExceptionHandler(
ex -> handleException(ex, handlerMethod, bindingContext, exchange))) return modelCompletion.then(() -> {
.otherwise(ex -> handleException(
ex, handlerMethod, bindingContext, exchange))); InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
invocable.setArgumentResolvers(getArgumentResolvers());
return invocable.invoke(exchange, bindingContext)
.doOnNext(result -> result.setExceptionHandler(exceptionHandler))
.otherwise(exceptionHandler);
});
} }
private List<SyncInvocableHandlerMethod> getInitBinderMethods(HandlerMethod handlerMethod) { private List<SyncInvocableHandlerMethod> getBinderMethods(HandlerMethod handlerMethod) {
Class<?> handlerType = handlerMethod.getBeanType(); Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.computeIfAbsent(handlerType, aClass -> Set<Method> methods = this.binderMethodCache.computeIfAbsent(handlerType, aClass ->
MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS)); MethodIntrospector.selectMethods(handlerType, BINDER_METHODS));
return methods.stream() return methods.stream()
.map(method -> { .map(method -> {
@ -343,8 +348,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
Class<?> handlerType = handlerMethod.getBeanType(); Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.modelAttributeCache.computeIfAbsent(handlerType, aClass -> Set<Method> methods = this.attributeMethodCache.computeIfAbsent(handlerType, aClass ->
MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS)); MethodIntrospector.selectMethods(handlerType, ATTRIBUTE_METHODS));
return methods.stream() return methods.stream()
.map(method -> { .map(method -> {
@ -359,51 +364,43 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod, private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
BindingContext bindingContext, ServerWebExchange exchange) { BindingContext bindingContext, ServerWebExchange exchange) {
InvocableHandlerMethod invocable = findExceptionHandler(handlerMethod, ex); ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache
if (invocable != null) { .computeIfAbsent(handlerMethod.getBeanType(), ExceptionHandlerMethodResolver::new);
Method method = resolver.resolveMethodByExceptionType(ex.getClass());
if (method != null) {
Object bean = handlerMethod.getBean();
InvocableHandlerMethod invocable = new InvocableHandlerMethod(bean, method);
invocable.setArgumentResolvers(getArgumentResolvers());
try { try {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod()); logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod());
} }
invocable.setArgumentResolvers(getArgumentResolvers());
bindingContext.getModel().asMap().clear(); bindingContext.getModel().asMap().clear();
return invocable.invoke(exchange, bindingContext, ex); return invocable.invoke(exchange, bindingContext, ex);
} }
catch (Throwable invocationEx) { catch (Throwable invocationEx) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke @ExceptionHandler method: " + invocable.getMethod(), logger.warn("Failed to invoke: " + invocable.getMethod(), invocationEx);
invocationEx);
} }
} }
} }
return Mono.error(ex);
}
protected InvocableHandlerMethod findExceptionHandler(HandlerMethod handlerMethod, Throwable exception) { return Mono.error(ex);
if (handlerMethod == null) {
return null;
}
Class<?> handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethodByExceptionType(exception.getClass());
return (method != null ? new InvocableHandlerMethod(handlerMethod.getBean(), method) : null);
} }
/** /**
* MethodFilter that matches {@link InitBinder @InitBinder} methods. * MethodFilter that matches {@link InitBinder @InitBinder} methods.
*/ */
public static final ReflectionUtils.MethodFilter INIT_BINDER_METHODS = method -> public static final ReflectionUtils.MethodFilter BINDER_METHODS = method ->
AnnotationUtils.findAnnotation(method, InitBinder.class) != null; AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
/** /**
* MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods. * MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
*/ */
public static final ReflectionUtils.MethodFilter MODEL_ATTRIBUTE_METHODS = method -> public static final ReflectionUtils.MethodFilter ATTRIBUTE_METHODS = method ->
(AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) && (AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&
(AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null); (AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null);

View File

@ -48,8 +48,8 @@ import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.INIT_BINDER_METHODS; import static org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.BINDER_METHODS;
import static org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.MODEL_ATTRIBUTE_METHODS; import static org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.ATTRIBUTE_METHODS;
/** /**
* Unit tests for {@link ModelInitializer}. * Unit tests for {@link ModelInitializer}.
@ -111,14 +111,14 @@ public class ModelInitializerTests {
private List<SyncInvocableHandlerMethod> getBinderMethods(Object controller) { private List<SyncInvocableHandlerMethod> getBinderMethods(Object controller) {
return MethodIntrospector return MethodIntrospector
.selectMethods(controller.getClass(), INIT_BINDER_METHODS).stream() .selectMethods(controller.getClass(), BINDER_METHODS).stream()
.map(method -> new SyncInvocableHandlerMethod(controller, method)) .map(method -> new SyncInvocableHandlerMethod(controller, method))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private List<InvocableHandlerMethod> getAttributeMethods(Object controller) { private List<InvocableHandlerMethod> getAttributeMethods(Object controller) {
return MethodIntrospector return MethodIntrospector
.selectMethods(controller.getClass(), MODEL_ATTRIBUTE_METHODS).stream() .selectMethods(controller.getClass(), ATTRIBUTE_METHODS).stream()
.map(method -> { .map(method -> {
InvocableHandlerMethod invocable = new InvocableHandlerMethod(controller, method); InvocableHandlerMethod invocable = new InvocableHandlerMethod(controller, method);
invocable.setArgumentResolvers(Collections.singletonList(new ModelArgumentResolver())); invocable.setArgumentResolvers(Collections.singletonList(new ModelArgumentResolver()));