Support @MessageExceptionHandler w/ @ControllerAdvice
This change adds support for global @MessageExceptionHandler methods with STOMP over WebSocket messages. Such methods can be added to @ControllerAdvice annotated components, much like @ExceptionHandler methods for Spring MVC. Issue: SPR-12696
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Represents a Spring-managed bean with cross-cutting functionality to be
|
||||
* applied to one or more Spring beans with annotation-based message
|
||||
* handling methods.
|
||||
*
|
||||
* <p>Component stereotypes such as
|
||||
* {@link org.springframework.stereotype.Controller @Controller} with annotation
|
||||
* handler methods often need cross-cutting functionality across all or a subset
|
||||
* of such annotated components. A primary example of this is the need for "global"
|
||||
* annotated exception handler methods but the concept applies more generally.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public interface MessagingAdviceBean extends Ordered {
|
||||
|
||||
/**
|
||||
* Return the type of the contained advice bean.
|
||||
* <p>If the bean type is a CGLIB-generated class, the original user-defined
|
||||
* class is returned.
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the advice bean instance, if necessary resolving a bean specified
|
||||
* by name through the BeanFactory.
|
||||
*/
|
||||
Object resolveBean();
|
||||
|
||||
/**
|
||||
* Whether this {@link MessagingAdviceBean} applies to the given bean type.
|
||||
* @param beanType the type of the bean to check
|
||||
*/
|
||||
boolean isApplicableToBeanType(Class<?> beanType);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -41,6 +41,8 @@ import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.handler.DestinationPatternsMessageCondition;
|
||||
import org.springframework.messaging.handler.HandlerMethod;
|
||||
import org.springframework.messaging.handler.HandlerMethodSelector;
|
||||
import org.springframework.messaging.handler.MessagingAdviceBean;
|
||||
import org.springframework.messaging.handler.annotation.support.AnnotationExceptionHandlerMethodResolver;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -87,6 +89,9 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, AbstractExceptionHandlerMethodResolver>(64);
|
||||
|
||||
private final Map<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
|
||||
new LinkedHashMap<MessagingAdviceBean, AbstractExceptionHandlerMethodResolver>(64);
|
||||
|
||||
|
||||
/**
|
||||
* When this property is configured only messages to destinations matching
|
||||
@@ -327,6 +332,25 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
*/
|
||||
protected abstract Set<String> getDirectLookupDestinations(T mapping);
|
||||
|
||||
/**
|
||||
* Sub-classes can invoke this method to populate the MessagingAdviceBean cache
|
||||
* (e.g. to support "global" {@code @MessageExceptionHandler}).
|
||||
* @since 4.2
|
||||
*/
|
||||
protected void initMessagingAdviceCache(List<MessagingAdviceBean> beans) {
|
||||
if (beans == null) {
|
||||
return;
|
||||
}
|
||||
for (MessagingAdviceBean bean : beans) {
|
||||
Class<?> beanType = bean.getBeanType();
|
||||
AnnotationExceptionHandlerMethodResolver resolver = new AnnotationExceptionHandlerMethodResolver(beanType);
|
||||
if (resolver.hasExceptionMappings()) {
|
||||
this.exceptionHandlerAdviceCache.put(bean, resolver);
|
||||
logger.info("Detected @MessageExceptionHandler methods in " + bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
@@ -464,21 +488,11 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
}
|
||||
|
||||
protected void processHandlerMethodException(HandlerMethod handlerMethod, Exception ex, Message<?> message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Searching methods to handle " + ex.getClass().getSimpleName());
|
||||
}
|
||||
Class<?> beanType = handlerMethod.getBeanType();
|
||||
AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
|
||||
if (resolver == null) {
|
||||
resolver = createExceptionHandlerMethodResolverFor(beanType);
|
||||
this.exceptionHandlerCache.put(beanType, resolver);
|
||||
}
|
||||
Method method = resolver.resolveMethod(ex);
|
||||
if (method == null) {
|
||||
InvocableHandlerMethod invocable = getExceptionHandlerMethod(handlerMethod, ex);
|
||||
if (invocable == null) {
|
||||
logger.error("Unhandled exception", ex);
|
||||
return;
|
||||
}
|
||||
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod.getBean(), method);
|
||||
invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invoking " + invocable.getShortLogMessage());
|
||||
@@ -499,6 +513,44 @@ public abstract class AbstractMethodMessageHandler<T>
|
||||
|
||||
protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Find an {@code @MessageExceptionHandler} method for the given exception.
|
||||
* The default implementation searches methods in the class hierarchy of the
|
||||
* HandlerMethod first and if not found, it continues searching for additional
|
||||
* {@code @MessageExceptionHandler} methods among the configured
|
||||
* {@linkplain org.springframework.messaging.handler.MessagingAdviceBean
|
||||
* MessagingAdviceBean}, if any.
|
||||
* @param handlerMethod the method where the exception was raised
|
||||
* @param exception the raised exception
|
||||
* @return a method to handle the exception, or {@code null}
|
||||
* @since 4.2
|
||||
*/
|
||||
protected InvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Searching methods to handle " + exception.getClass().getSimpleName());
|
||||
}
|
||||
Class<?> beanType = handlerMethod.getBeanType();
|
||||
AbstractExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
|
||||
if (resolver == null) {
|
||||
resolver = createExceptionHandlerMethodResolverFor(beanType);
|
||||
this.exceptionHandlerCache.put(beanType, resolver);
|
||||
}
|
||||
Method method = resolver.resolveMethod(exception);
|
||||
if (method != null) {
|
||||
return new InvocableHandlerMethod(handlerMethod.getBean(), method);
|
||||
}
|
||||
for (MessagingAdviceBean advice : this.exceptionHandlerAdviceCache.keySet()) {
|
||||
if (advice.isApplicableToBeanType(beanType)) {
|
||||
resolver = this.exceptionHandlerAdviceCache.get(advice);
|
||||
method = resolver.resolveMethod(exception);
|
||||
if (method != null) {
|
||||
return new InvocableHandlerMethod(advice.resolveBean(), method);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void handleNoMatch(Set<T> ts, String lookupDestination, Message<?> message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No matching methods.");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -225,9 +225,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
|
||||
@Bean
|
||||
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
|
||||
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
|
||||
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
|
||||
|
||||
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler();
|
||||
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
|
||||
handler.setMessageConverter(brokerMessageConverter());
|
||||
handler.setValidator(simpValidator());
|
||||
@@ -247,6 +245,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected method for plugging in a custom sub-class of
|
||||
* {@link org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler
|
||||
* SimpAnnotationMethodMessageHandler}.
|
||||
* @since 4.2
|
||||
*/
|
||||
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler() {
|
||||
return new SimpAnnotationMethodMessageHandler(clientInboundChannel(),
|
||||
clientOutboundChannel(), brokerMessagingTemplate());
|
||||
}
|
||||
|
||||
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user