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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.DestinationPatternsMessageCondition;
|
||||||
import org.springframework.messaging.handler.HandlerMethod;
|
import org.springframework.messaging.handler.HandlerMethod;
|
||||||
import org.springframework.messaging.handler.HandlerMethodSelector;
|
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.MessageBuilder;
|
||||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@@ -87,6 +89,9 @@ public abstract class AbstractMethodMessageHandler<T>
|
|||||||
private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache =
|
private final Map<Class<?>, AbstractExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||||
new ConcurrentHashMap<Class<?>, AbstractExceptionHandlerMethodResolver>(64);
|
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
|
* 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);
|
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
|
@Override
|
||||||
public void handleMessage(Message<?> message) throws MessagingException {
|
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) {
|
protected void processHandlerMethodException(HandlerMethod handlerMethod, Exception ex, Message<?> message) {
|
||||||
if (logger.isDebugEnabled()) {
|
InvocableHandlerMethod invocable = getExceptionHandlerMethod(handlerMethod, ex);
|
||||||
logger.debug("Searching methods to handle " + ex.getClass().getSimpleName());
|
if (invocable == null) {
|
||||||
}
|
|
||||||
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) {
|
|
||||||
logger.error("Unhandled exception", ex);
|
logger.error("Unhandled exception", ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod.getBean(), method);
|
|
||||||
invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
|
invocable.setMessageMethodArgumentResolvers(this.argumentResolvers);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Invoking " + invocable.getShortLogMessage());
|
logger.debug("Invoking " + invocable.getShortLogMessage());
|
||||||
@@ -499,6 +513,44 @@ public abstract class AbstractMethodMessageHandler<T>
|
|||||||
|
|
||||||
protected abstract AbstractExceptionHandlerMethodResolver createExceptionHandlerMethodResolverFor(Class<?> beanType);
|
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) {
|
protected void handleNoMatch(Set<T> ts, String lookupDestination, Message<?> message) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("No matching methods.");
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
@Bean
|
||||||
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
|
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
|
||||||
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
|
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler();
|
||||||
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
|
|
||||||
|
|
||||||
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
|
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
|
||||||
handler.setMessageConverter(brokerMessageConverter());
|
handler.setMessageConverter(brokerMessageConverter());
|
||||||
handler.setValidator(simpValidator());
|
handler.setValidator(simpValidator());
|
||||||
@@ -247,6 +245,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
|||||||
return handler;
|
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) {
|
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
|
|
||||||
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
|
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
@@ -34,11 +31,13 @@ import org.springframework.beans.factory.config.CustomScopeConfigurer;
|
|||||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||||
import org.springframework.beans.factory.support.ManagedList;
|
import org.springframework.beans.factory.support.ManagedList;
|
||||||
import org.springframework.beans.factory.support.ManagedMap;
|
import org.springframework.beans.factory.support.ManagedMap;
|
||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
import org.springframework.beans.factory.xml.ParserContext;
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
|
||||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||||
import org.springframework.messaging.converter.DefaultContentTypeResolver;
|
import org.springframework.messaging.converter.DefaultContentTypeResolver;
|
||||||
@@ -46,13 +45,13 @@ import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
|||||||
import org.springframework.messaging.converter.StringMessageConverter;
|
import org.springframework.messaging.converter.StringMessageConverter;
|
||||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
import org.springframework.messaging.simp.SimpSessionScope;
|
import org.springframework.messaging.simp.SimpSessionScope;
|
||||||
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
|
||||||
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
|
||||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||||
import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
|
import org.springframework.messaging.simp.user.DefaultUserDestinationResolver;
|
||||||
import org.springframework.messaging.simp.user.DefaultUserSessionRegistry;
|
import org.springframework.messaging.simp.user.DefaultUserSessionRegistry;
|
||||||
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
|
import org.springframework.messaging.simp.user.UserDestinationMessageHandler;
|
||||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||||
|
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@@ -64,6 +63,7 @@ import org.springframework.web.socket.WebSocketHandler;
|
|||||||
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
|
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
|
||||||
import org.springframework.web.socket.messaging.StompSubProtocolHandler;
|
import org.springframework.web.socket.messaging.StompSubProtocolHandler;
|
||||||
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
|
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
|
||||||
|
import org.springframework.web.socket.messaging.WebSocketAnnotationMethodMessageHandler;
|
||||||
import org.springframework.web.socket.server.support.OriginHandshakeInterceptor;
|
import org.springframework.web.socket.server.support.OriginHandshakeInterceptor;
|
||||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
|
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
|
||||||
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
|
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
|
||||||
@@ -426,7 +426,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
values.add("destinationPrefixes", Arrays.asList(StringUtils.tokenizeToStringArray(prefixAttribute, ",")));
|
values.add("destinationPrefixes", Arrays.asList(StringUtils.tokenizeToStringArray(prefixAttribute, ",")));
|
||||||
values.add("messageConverter", converter);
|
values.add("messageConverter", converter);
|
||||||
|
|
||||||
RootBeanDefinition beanDef = new RootBeanDefinition(SimpAnnotationMethodMessageHandler.class, cavs, values);
|
RootBeanDefinition beanDef = new RootBeanDefinition(WebSocketAnnotationMethodMessageHandler.class, cavs, values);
|
||||||
if (messageBrokerElement.hasAttribute("path-matcher")) {
|
if (messageBrokerElement.hasAttribute("path-matcher")) {
|
||||||
String pathMatcherRef = messageBrokerElement.getAttribute("path-matcher");
|
String pathMatcherRef = messageBrokerElement.getAttribute("path-matcher");
|
||||||
beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
|
beanDef.getPropertyValues().add("pathMatcher", new RuntimeBeanReference(pathMatcherRef));
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||||
import org.springframework.messaging.simp.SimpSessionScope;
|
import org.springframework.messaging.simp.SimpSessionScope;
|
||||||
|
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
||||||
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
|
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
|
||||||
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
|
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
|
||||||
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
|
||||||
@@ -30,6 +31,7 @@ import org.springframework.web.socket.WebSocketHandler;
|
|||||||
import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
|
import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
|
||||||
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
|
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
|
||||||
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
|
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
|
||||||
|
import org.springframework.web.socket.messaging.WebSocketAnnotationMethodMessageHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends {@link AbstractMessageBrokerConfiguration} and adds configuration for
|
* Extends {@link AbstractMessageBrokerConfiguration} and adds configuration for
|
||||||
@@ -48,6 +50,12 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac
|
|||||||
private WebSocketTransportRegistration transportRegistration;
|
private WebSocketTransportRegistration transportRegistration;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler() {
|
||||||
|
return new WebSocketAnnotationMethodMessageHandler(clientInboundChannel(),
|
||||||
|
clientOutboundChannel(), brokerMessagingTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public HandlerMapping stompWebSocketHandlerMapping() {
|
public HandlerMapping stompWebSocketHandlerMapping() {
|
||||||
WebSocketHandler handler = subProtocolWebSocketHandler();
|
WebSocketHandler handler = subProtocolWebSocketHandler();
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.web.socket.messaging;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
|
import org.springframework.messaging.MessageChannel;
|
||||||
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
|
import org.springframework.messaging.handler.MessagingAdviceBean;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||||
|
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
||||||
|
import org.springframework.web.method.ControllerAdviceBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sub-class of {@link SimpAnnotationMethodMessageHandler} to provide support
|
||||||
|
* for {@link org.springframework.web.bind.annotation.ControllerAdvice
|
||||||
|
* ControllerAdvice} with global {@code @MessageExceptionHandler} methods.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public class WebSocketAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
|
||||||
|
|
||||||
|
|
||||||
|
public WebSocketAnnotationMethodMessageHandler(SubscribableChannel clientInChannel, MessageChannel clientOutChannel,
|
||||||
|
SimpMessageSendingOperations brokerTemplate) {
|
||||||
|
|
||||||
|
super(clientInChannel, clientOutChannel, brokerTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
initControllerAdviceCache();
|
||||||
|
super.afterPropertiesSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initControllerAdviceCache() {
|
||||||
|
if (getApplicationContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Looking for @MessageExceptionHandler mappings: " + getApplicationContext());
|
||||||
|
}
|
||||||
|
List<ControllerAdviceBean> controllerAdvice = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
|
||||||
|
AnnotationAwareOrderComparator.sort(controllerAdvice);
|
||||||
|
initMessagingAdviceCache(MessagingControllerAdviceBean.createFromList(controllerAdvice));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapt ControllerAdviceBean to MessagingAdviceBean.
|
||||||
|
*/
|
||||||
|
private static class MessagingControllerAdviceBean implements MessagingAdviceBean {
|
||||||
|
|
||||||
|
private final ControllerAdviceBean adviceBean;
|
||||||
|
|
||||||
|
|
||||||
|
private MessagingControllerAdviceBean(ControllerAdviceBean adviceBean) {
|
||||||
|
this.adviceBean = adviceBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<MessagingAdviceBean> createFromList(List<ControllerAdviceBean> controllerAdvice) {
|
||||||
|
List<MessagingAdviceBean> messagingAdvice = new ArrayList<MessagingAdviceBean>(controllerAdvice.size());
|
||||||
|
for (ControllerAdviceBean bean : controllerAdvice) {
|
||||||
|
messagingAdvice.add(new MessagingControllerAdviceBean(bean));
|
||||||
|
}
|
||||||
|
return messagingAdvice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getBeanType() {
|
||||||
|
return this.adviceBean.getBeanType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object resolveBean() {
|
||||||
|
return this.adviceBean.resolveBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicableToBeanType(Class<?> beanType) {
|
||||||
|
return this.adviceBean.isApplicableToBeanType(beanType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return this.adviceBean.getOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -696,7 +696,7 @@
|
|||||||
<xsd:documentation><![CDATA[
|
<xsd:documentation><![CDATA[
|
||||||
Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
|
Configures HandlerMethodArgumentResolver types to support custom controller method argument types.
|
||||||
Using this option does not override the built-in support for resolving handler method arguments.
|
Using this option does not override the built-in support for resolving handler method arguments.
|
||||||
To customize the built-in support for argument resolution configure SimpAnnotationMethodMessageHandler directly.
|
To customize the built-in support for argument resolution configure WebSocketAnnotationMethodMessageHandler directly.
|
||||||
]]></xsd:documentation>
|
]]></xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
@@ -728,7 +728,7 @@
|
|||||||
<xsd:documentation><![CDATA[
|
<xsd:documentation><![CDATA[
|
||||||
Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
|
Configures HandlerMethodReturnValueHandler types to support custom controller method return value handling.
|
||||||
Using this option does not override the built-in support for handling return values.
|
Using this option does not override the built-in support for handling return values.
|
||||||
To customize the built-in support for handling return values configure SimpAnnotationMethodMessageHandler directly.
|
To customize the built-in support for handling return values configure WebSocketAnnotationMethodMessageHandler directly.
|
||||||
]]></xsd:documentation>
|
]]></xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
<xsd:complexType>
|
<xsd:complexType>
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.web.socket.messaging;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.MessageChannel;
|
||||||
|
import org.springframework.messaging.SubscribableChannel;
|
||||||
|
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||||
|
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||||
|
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link WebSocketAnnotationMethodMessageHandler}.
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class WebSocketAnnotationMethodMessageHandlerTests {
|
||||||
|
|
||||||
|
private TestWebSocketAnnotationMethodMessageHandler messageHandler;
|
||||||
|
|
||||||
|
private StaticApplicationContext applicationContext;
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
this.applicationContext = new StaticApplicationContext();
|
||||||
|
this.applicationContext.registerSingleton("controller", TestController.class);
|
||||||
|
this.applicationContext.registerSingleton("controllerAdvice", TestControllerAdvice.class);
|
||||||
|
this.applicationContext.refresh();
|
||||||
|
|
||||||
|
SubscribableChannel channel = Mockito.mock(SubscribableChannel.class);
|
||||||
|
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
|
||||||
|
|
||||||
|
this.messageHandler = new TestWebSocketAnnotationMethodMessageHandler(brokerTemplate, channel, channel);
|
||||||
|
this.messageHandler.setApplicationContext(this.applicationContext);
|
||||||
|
this.messageHandler.afterPropertiesSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void globalException() throws Exception {
|
||||||
|
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
||||||
|
headers.setSessionId("session1");
|
||||||
|
headers.setSessionAttributes(new ConcurrentHashMap<>());
|
||||||
|
headers.setDestination("/exception");
|
||||||
|
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||||
|
this.messageHandler.handleMessage(message);
|
||||||
|
|
||||||
|
TestControllerAdvice controllerAdvice = this.applicationContext.getBean(TestControllerAdvice.class);
|
||||||
|
assertTrue(controllerAdvice.isExceptionHandled());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
private static class TestController {
|
||||||
|
|
||||||
|
@MessageMapping("/exception")
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public void handleWithSimulatedException() {
|
||||||
|
throw new IllegalStateException("simulated exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
private static class TestControllerAdvice {
|
||||||
|
|
||||||
|
private boolean exceptionHandled;
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isExceptionHandled() {
|
||||||
|
return this.exceptionHandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MessageExceptionHandler
|
||||||
|
public void handleException(IllegalStateException ex) {
|
||||||
|
this.exceptionHandled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class TestWebSocketAnnotationMethodMessageHandler extends WebSocketAnnotationMethodMessageHandler {
|
||||||
|
|
||||||
|
|
||||||
|
public TestWebSocketAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
|
||||||
|
SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
|
||||||
|
|
||||||
|
super(clientInboundChannel, clientOutboundChannel, brokerTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerHandler(Object handler) {
|
||||||
|
super.detectHandlerMethods(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user