Browse Source
Split out the mechanics of invoking a HandlerMethod and handling the result into a separate helper class. See gh-21987pull/22513/head
3 changed files with 235 additions and 121 deletions
@ -0,0 +1,206 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2019 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.messaging.handler.invocation.reactive; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.core.ReactiveAdapterRegistry; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
import org.springframework.messaging.Message; |
||||||
|
import org.springframework.messaging.handler.HandlerMethod; |
||||||
|
import org.springframework.messaging.handler.MessagingAdviceBean; |
||||||
|
import org.springframework.messaging.handler.invocation.AbstractExceptionHandlerMethodResolver; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* Help to initialize and invoke an {@link InvocableHandlerMethod}, and to then |
||||||
|
* apply return value handling and exception handling. Holds all necessary |
||||||
|
* configuration necessary to do so. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 5.2 |
||||||
|
*/ |
||||||
|
class InvocableHelper { |
||||||
|
|
||||||
|
private static Log logger = LogFactory.getLog(InvocableHelper.class); |
||||||
|
|
||||||
|
|
||||||
|
private final HandlerMethodArgumentResolverComposite argumentResolvers = |
||||||
|
new HandlerMethodArgumentResolverComposite(); |
||||||
|
|
||||||
|
private final HandlerMethodReturnValueHandlerComposite returnValueHandlers = |
||||||
|
new HandlerMethodReturnValueHandlerComposite(); |
||||||
|
|
||||||
|
private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); |
||||||
|
|
||||||
|
private final Function<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionMethodResolverFactory; |
||||||
|
|
||||||
|
private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache = |
||||||
|
new ConcurrentHashMap<>(64); |
||||||
|
|
||||||
|
private final Map<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = |
||||||
|
new LinkedHashMap<>(64); |
||||||
|
|
||||||
|
|
||||||
|
public InvocableHelper( |
||||||
|
Function<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionMethodResolverFactory) { |
||||||
|
|
||||||
|
this.exceptionMethodResolverFactory = exceptionMethodResolverFactory; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the arguments resolvers to use for message handling and exception |
||||||
|
* handling methods. |
||||||
|
*/ |
||||||
|
public void addArgumentResolvers(List<? extends HandlerMethodArgumentResolver> resolvers) { |
||||||
|
this.argumentResolvers.addResolvers(resolvers); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the return value handlers to use for message handling and exception |
||||||
|
* handling methods. |
||||||
|
*/ |
||||||
|
public void addReturnValueHandlers(List<? extends HandlerMethodReturnValueHandler> handlers) { |
||||||
|
this.returnValueHandlers.addHandlers(handlers); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configure the registry for adapting various reactive types. |
||||||
|
* <p>By default this is an instance of {@link ReactiveAdapterRegistry} with |
||||||
|
* default settings. |
||||||
|
*/ |
||||||
|
public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { |
||||||
|
Assert.notNull(registry, "ReactiveAdapterRegistry is required"); |
||||||
|
this.reactiveAdapterRegistry = registry; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the configured registry for adapting reactive types. |
||||||
|
*/ |
||||||
|
public ReactiveAdapterRegistry getReactiveAdapterRegistry() { |
||||||
|
return this.reactiveAdapterRegistry; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Method to populate the MessagingAdviceBean cache (e.g. to support "global" |
||||||
|
* {@code @MessageExceptionHandler}). |
||||||
|
*/ |
||||||
|
public void registerExceptionHandlerAdvice( |
||||||
|
MessagingAdviceBean bean, AbstractExceptionHandlerMethodResolver resolver) { |
||||||
|
|
||||||
|
this.exceptionHandlerAdviceCache.put(bean, resolver); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create {@link InvocableHandlerMethod} with the configured arg resolvers. |
||||||
|
* @param handlerMethod the target handler method to invoke |
||||||
|
* @return the created instance |
||||||
|
*/ |
||||||
|
|
||||||
|
public InvocableHandlerMethod initMessageMappingMethod(HandlerMethod handlerMethod) { |
||||||
|
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod); |
||||||
|
invocable.setArgumentResolvers(this.argumentResolvers.getResolvers()); |
||||||
|
return invocable; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find an exception handling method for the given exception. |
||||||
|
* <p>The default implementation searches methods in the class hierarchy of |
||||||
|
* the HandlerMethod first and if not found, it continues searching for |
||||||
|
* additional handling methods registered via |
||||||
|
* {@link #registerExceptionHandlerAdvice}. |
||||||
|
* @param handlerMethod the method where the exception was raised |
||||||
|
* @param ex the exception raised or signaled |
||||||
|
* @return a method to handle the exception, or {@code null} |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public InvocableHandlerMethod initExceptionHandlerMethod(HandlerMethod handlerMethod, Throwable ex) { |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Searching for methods to handle " + ex.getClass().getSimpleName()); |
||||||
|
} |
||||||
|
Class<?> beanType = handlerMethod.getBeanType(); |
||||||
|
AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType); |
||||||
|
if (resolver == null) { |
||||||
|
resolver = this.exceptionMethodResolverFactory.apply(beanType); |
||||||
|
this.exceptionHandlerCache.put(beanType, resolver); |
||||||
|
} |
||||||
|
InvocableHandlerMethod exceptionHandlerMethod = null; |
||||||
|
Method method = resolver.resolveMethod(ex); |
||||||
|
if (method != null) { |
||||||
|
exceptionHandlerMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method); |
||||||
|
} |
||||||
|
else { |
||||||
|
for (MessagingAdviceBean advice : this.exceptionHandlerAdviceCache.keySet()) { |
||||||
|
if (advice.isApplicableToBeanType(beanType)) { |
||||||
|
resolver = this.exceptionHandlerAdviceCache.get(advice); |
||||||
|
method = resolver.resolveMethod(ex); |
||||||
|
if (method != null) { |
||||||
|
exceptionHandlerMethod = new InvocableHandlerMethod(advice.resolveBean(), method); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (exceptionHandlerMethod != null) { |
||||||
|
logger.debug("Found exception handler " + exceptionHandlerMethod.getShortLogMessage()); |
||||||
|
exceptionHandlerMethod.setArgumentResolvers(this.argumentResolvers.getResolvers()); |
||||||
|
} |
||||||
|
else { |
||||||
|
logger.error("No exception handling method", ex); |
||||||
|
} |
||||||
|
return exceptionHandlerMethod; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Mono<Void> handleMessage(HandlerMethod handlerMethod, Message<?> message) { |
||||||
|
InvocableHandlerMethod invocable = initMessageMappingMethod(handlerMethod); |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Invoking " + invocable.getShortLogMessage()); |
||||||
|
} |
||||||
|
return invocable.invoke(message) |
||||||
|
.flatMap(returnValue -> handleReturnValue(returnValue, invocable, message)) |
||||||
|
.onErrorResume(ex -> { |
||||||
|
InvocableHandlerMethod exHandler = initExceptionHandlerMethod(handlerMethod, ex); |
||||||
|
if (exHandler == null) { |
||||||
|
return Mono.error(ex); |
||||||
|
} |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Invoking " + exHandler.getShortLogMessage()); |
||||||
|
} |
||||||
|
return exHandler.invoke(message, ex) |
||||||
|
.flatMap(returnValue -> handleReturnValue(returnValue, exHandler, message)); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private Mono<Void> handleReturnValue( |
||||||
|
@Nullable Object returnValue, HandlerMethod handlerMethod, Message<?> message) { |
||||||
|
|
||||||
|
MethodParameter returnType = handlerMethod.getReturnType(); |
||||||
|
return this.returnValueHandlers.handleReturnValue(returnValue, returnType, message); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue