Add @ReplyTo/@ReplyToUser, remove deps on spring-web

This commit is contained in:
Rossen Stoyanchev 2013-07-16 22:07:46 -04:00
parent 55dae74f15
commit 078cfb3e78
30 changed files with 1385 additions and 307 deletions

View File

@ -320,7 +320,6 @@ project("spring-messaging") {
compile(project(":spring-beans"))
compile(project(":spring-core"))
compile(project(":spring-context"))
optional(project(":spring-web")) // TODO: MediaType/HandlerMethod/EHMR
optional(project(":spring-websocket"))
optional("com.fasterxml.jackson.core:jackson-databind:2.2.0")
optional("org.projectreactor:reactor-core:1.0.0.BUILD-SNAPSHOT")

View File

@ -32,10 +32,9 @@ import java.lang.annotation.Target;
@Documented
public @interface ReplyTo {
/**
* The destination value for the reply.
* The destination for a message created from the return value of a method.
*/
String value();
String[] value() default {};
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2002-2013 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.annotation.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.ExceptionDepthComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.method.HandlerMethodSelector;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* Discovers annotated exception handling methods in a given class type, including all
* super types, and helps to resolve an Exception to a method that can handle it. The
* exception types supported by a given method can also be discovered from the method
* signature.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ExceptionHandlerMethodResolver {
private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
private final Map<Class<? extends Throwable>, Method> mappedMethods =
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache =
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
/**
* A constructor that finds {@link MessageExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHOD_FILTER)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation
* first and as a fall-back from the method signature.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
detectAnnotationExceptionMappings(method, result);
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
return result;
}
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
MessageExceptionHandler annot = AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class);
result.addAll(Arrays.asList(annot.value()));
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException(
"Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" +
oldMethod + ", " + method + "}.");
}
}
/**
* Whether the contained type has any exception mappings.
*/
public boolean hasExceptionMappings() {
return (this.mappedMethods.size() > 0);
}
/**
* Find a method to handle the given exception.
* Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
* @return a method to handle the exception or {@code null}
*/
public Method resolveMethod(Exception exception) {
Class<? extends Exception> exceptionType = exception.getClass();
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND);
}
return method != NO_METHOD_FOUND ? method : null;
}
/**
* Return the method mapped to the given exception type or {@code null}.
*/
private Method getMappedMethod(Class<? extends Exception> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
for(Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
return mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
/** A filter for selecting annotated exception handling methods. */
public final static MethodFilter EXCEPTION_HANDLER_METHOD_FILTER = new MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class) != null;
}
};
}

View File

@ -19,21 +19,26 @@ package org.springframework.messaging.handler.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.MessageBody;
import org.springframework.messaging.handler.method.MessageArgumentResolver;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.util.Assert;
/**
* TODO
*
* <p>This {@link HandlerMethodArgumentResolver} should be ordered last as it supports all
* types and does not require the {@link MessageBody} annotation.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageBodyArgumentResolver implements MessageArgumentResolver {
public class MessageBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final MessageConverter<?> converter;
public MessageBodyArgumentResolver(MessageConverter<?> converter) {
public MessageBodyMethodArgumentResolver(MessageConverter<?> converter) {
Assert.notNull(converter, "converter is required");
this.converter = converter;
}

View File

@ -1,63 +0,0 @@
/*
* Copyright 2002-2013 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.annotation.support;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageExceptionHandlerMethodResolver extends ExceptionHandlerMethodResolver {
public MessageExceptionHandlerMethodResolver(Class<?> handlerType) {
super(handlerType);
}
@Override
protected MethodFilter getExceptionHandlerMethods() {
return MESSAGE_EXCEPTION_HANDLER_METHODS;
}
@Override
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
MessageExceptionHandler annotation = AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class);
result.addAll(Arrays.asList(annotation.value()));
}
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
public final static MethodFilter MESSAGE_EXCEPTION_HANDLER_METHODS = new MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class) != null;
}
};
}

View File

@ -0,0 +1,285 @@
/*
* Copyright 2002-2012 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.method;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Encapsulates information about a bean method consisting of a
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. Provides
* convenient access to method parameters, the method return value, method
* annotations.
*
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy
* bean, prototype bean). Use {@link #createWithResolvedBean()} to obtain an
* {@link HandlerMethod} instance with a bean instance initialized through the
* bean factory.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 4.0
*/
public class HandlerMethod {
/** Logger that is available to subclasses */
protected final Log logger = LogFactory.getLog(HandlerMethod.class);
private final Object bean;
private final Method method;
private final BeanFactory beanFactory;
private final MethodParameter[] parameters;
private final Method bridgedMethod;
/**
* Create an instance from a bean instance and a method.
*/
public HandlerMethod(Object bean, Method method) {
Assert.notNull(bean, "bean is required");
Assert.notNull(method, "method is required");
this.bean = bean;
this.beanFactory = null;
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
}
private MethodParameter[] initMethodParameters() {
int count = this.bridgedMethod.getParameterTypes().length;
MethodParameter[] result = new MethodParameter[count];
for (int i = 0; i < count; i++) {
result[i] = new HandlerMethodParameter(i);
}
return result;
}
/**
* Create an instance from a bean instance, method name, and parameter types.
* @throws NoSuchMethodException when the method cannot be found
*/
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
Assert.notNull(bean, "bean is required");
Assert.notNull(methodName, "method is required");
this.bean = bean;
this.beanFactory = null;
this.method = bean.getClass().getMethod(methodName, parameterTypes);
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
}
/**
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
* The method {@link #createWithResolvedBean()} may be used later to
* re-create the {@code HandlerMethod} with an initialized the bean.
*/
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
Assert.hasText(beanName, "beanName is required");
Assert.notNull(beanFactory, "beanFactory is required");
Assert.notNull(method, "method is required");
Assert.isTrue(beanFactory.containsBean(beanName),
"Bean factory [" + beanFactory + "] does not contain bean [" + beanName + "]");
this.bean = beanName;
this.beanFactory = beanFactory;
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
}
/**
* Copy constructor for use in sub-classes.
*/
protected HandlerMethod(HandlerMethod handlerMethod) {
Assert.notNull(handlerMethod, "HandlerMethod is required");
this.bean = handlerMethod.bean;
this.beanFactory = handlerMethod.beanFactory;
this.method = handlerMethod.method;
this.bridgedMethod = handlerMethod.bridgedMethod;
this.parameters = handlerMethod.parameters;
}
/**
* Re-create HandlerMethod with the resolved handler.
*/
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
Assert.notNull(handlerMethod, "handlerMethod is required");
Assert.notNull(handler, "handler is required");
this.bean = handler;
this.beanFactory = handlerMethod.beanFactory;
this.method = handlerMethod.method;
this.bridgedMethod = handlerMethod.bridgedMethod;
this.parameters = handlerMethod.parameters;
}
/**
* Returns the bean for this handler method.
*/
public Object getBean() {
return this.bean;
}
/**
* Returns the method for this handler method.
*/
public Method getMethod() {
return this.method;
}
/**
* Returns the type of the handler for this handler method.
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
*/
public Class<?> getBeanType() {
Class<?> clazz = (this.bean instanceof String)
? this.beanFactory.getType((String) this.bean) : this.bean.getClass();
return ClassUtils.getUserClass(clazz);
}
/**
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
* Otherwise it returns the same method as {@link #getMethod()}.
*/
protected Method getBridgedMethod() {
return this.bridgedMethod;
}
/**
* Returns the method parameters for this handler method.
*/
public MethodParameter[] getMethodParameters() {
return this.parameters;
}
/**
* Return the HandlerMethod return type.
*/
public MethodParameter getReturnType() {
return new HandlerMethodParameter(-1);
}
/**
* Return the actual return value type.
*/
public MethodParameter getReturnValueType(Object returnValue) {
return new ReturnValueMethodParameter(returnValue);
}
/**
* Returns {@code true} if the method return type is void, {@code false} otherwise.
*/
public boolean isVoid() {
return Void.TYPE.equals(getReturnType().getParameterType());
}
/**
* Returns a single annotation on the underlying method traversing its super methods if no
* annotation can be found on the given method itself.
* @param annotationType the type of annotation to introspect the method for.
* @return the annotation, or {@code null} if none found
*/
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return AnnotationUtils.findAnnotation(this.bridgedMethod, annotationType);
}
/**
* If the provided instance contains a bean name rather than an object instance, the bean name is resolved
* before a {@link HandlerMethod} is created and returned.
*/
public HandlerMethod createWithResolvedBean() {
Object handler = this.bean;
if (this.bean instanceof String) {
String beanName = (String) this.bean;
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && o instanceof HandlerMethod) {
HandlerMethod other = (HandlerMethod) o;
return this.bean.equals(other.bean) && this.method.equals(other.method);
}
return false;
}
@Override
public int hashCode() {
return 31 * this.bean.hashCode() + this.method.hashCode();
}
@Override
public String toString() {
return method.toGenericString();
}
/**
* A MethodParameter with HandlerMethod-specific behavior.
*/
private class HandlerMethodParameter extends MethodParameter {
protected HandlerMethodParameter(int index) {
super(HandlerMethod.this.bridgedMethod, index);
}
@Override
public Class<?> getDeclaringClass() {
return HandlerMethod.this.getBeanType();
}
@Override
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
return HandlerMethod.this.getMethodAnnotation(annotationType);
}
}
/**
* A MethodParameter for a HandlerMethod return type based on an actual return value.
*/
private class ReturnValueMethodParameter extends HandlerMethodParameter {
private final Object returnValue;
public ReturnValueMethodParameter(Object returnValue) {
super(-1);
this.returnValue = returnValue;
}
@Override
public Class<?> getParameterType() {
return (this.returnValue != null) ? this.returnValue.getClass() : super.getParameterType();
}
}
}

View File

@ -27,7 +27,7 @@ import org.springframework.messaging.Message;
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface MessageArgumentResolver {
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is

View File

@ -30,32 +30,32 @@ import org.springframework.util.Assert;
/**
* Resolves method parameters by delegating to a list of registered
* {@link MessageArgumentResolver}. Previously resolved method parameters are cached
* {@link HandlerMethodArgumentResolver}. Previously resolved method parameters are cached
* for faster lookups.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageArgumentResolverComposite implements MessageArgumentResolver {
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
protected final Log logger = LogFactory.getLog(getClass());
private final List<MessageArgumentResolver> argumentResolvers = new LinkedList<MessageArgumentResolver>();
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<HandlerMethodArgumentResolver>();
private final Map<MethodParameter, MessageArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<MethodParameter, MessageArgumentResolver>(256);
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
/**
* Return a read-only list with the contained resolvers, or an empty list.
*/
public List<MessageArgumentResolver> getResolvers() {
public List<HandlerMethodArgumentResolver> getResolvers() {
return Collections.unmodifiableList(this.argumentResolvers);
}
/**
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
* {@link MessageArgumentResolver}.
* {@link HandlerMethodArgumentResolver}.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
@ -63,24 +63,24 @@ public class MessageArgumentResolverComposite implements MessageArgumentResolver
}
/**
* Iterate over registered {@link MessageArgumentResolver}s and invoke the one that supports it.
* @exception IllegalStateException if no suitable {@link MessageArgumentResolver} is found.
* Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
* @exception IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
*/
@Override
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
MessageArgumentResolver resolver = getArgumentResolver(parameter);
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
return resolver.resolveArgument(parameter, message);
}
/**
* Find a registered {@link MessageArgumentResolver} that supports the given method parameter.
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
*/
private MessageArgumentResolver getArgumentResolver(MethodParameter parameter) {
MessageArgumentResolver result = this.argumentResolverCache.get(parameter);
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (MessageArgumentResolver resolver : this.argumentResolvers) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
@ -92,19 +92,19 @@ public class MessageArgumentResolverComposite implements MessageArgumentResolver
}
/**
* Add the given {@link MessageArgumentResolver}.
* Add the given {@link HandlerMethodArgumentResolver}.
*/
public MessageArgumentResolverComposite addResolver(MessageArgumentResolver argumentResolver) {
public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver argumentResolver) {
this.argumentResolvers.add(argumentResolver);
return this;
}
/**
* Add the given {@link MessageArgumentResolver}s.
* Add the given {@link HandlerMethodArgumentResolver}s.
*/
public MessageArgumentResolverComposite addResolvers(List<? extends MessageArgumentResolver> argumentResolvers) {
public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
if (argumentResolvers != null) {
for (MessageArgumentResolver resolver : argumentResolvers) {
for (HandlerMethodArgumentResolver resolver : argumentResolvers) {
this.argumentResolvers.add(resolver);
}
}

View File

@ -27,7 +27,7 @@ import org.springframework.messaging.Message;
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface MessageReturnValueHandler {
public interface HandlerMethodReturnValueHandler {
/**
* Whether the given {@linkplain MethodParameter method return type} is

View File

@ -28,25 +28,25 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class MessageReturnValueHandlerComposite implements MessageReturnValueHandler {
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
private final List<MessageReturnValueHandler> returnValueHandlers = new ArrayList<MessageReturnValueHandler>();
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
/**
* Add the given {@link MessageReturnValueHandler}.
* Add the given {@link HandlerMethodReturnValueHandler}.
*/
public MessageReturnValueHandlerComposite addHandler(MessageReturnValueHandler returnValuehandler) {
public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValuehandler) {
this.returnValueHandlers.add(returnValuehandler);
return this;
}
/**
* Add the given {@link MessageReturnValueHandler}s.
* Add the given {@link HandlerMethodReturnValueHandler}s.
*/
public MessageReturnValueHandlerComposite addHandlers(List<? extends MessageReturnValueHandler> handlers) {
public HandlerMethodReturnValueHandlerComposite addHandlers(List<? extends HandlerMethodReturnValueHandler> handlers) {
if (handlers != null) {
for (MessageReturnValueHandler handler : handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
this.returnValueHandlers.add(handler);
}
}
@ -58,8 +58,8 @@ public class MessageReturnValueHandlerComposite implements MessageReturnValueHan
return getReturnValueHandler(returnType) != null;
}
private MessageReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (MessageReturnValueHandler handler : this.returnValueHandlers) {
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
@ -71,7 +71,7 @@ public class MessageReturnValueHandlerComposite implements MessageReturnValueHan
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
throws Exception {
MessageReturnValueHandler handler = getReturnValueHandler(returnType);
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
handler.handleReturnValue(returnValue, returnType, message);
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2013 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.method;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* Defines the algorithm for searching handler methods exhaustively including interfaces and parent
* classes while also dealing with parameterized methods as well as interface and class-based proxies.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class HandlerMethodSelector {
/**
* Selects handler methods for the given handler type. Callers of this method define handler methods
* of interest through the {@link MethodFilter} parameter.
*
* @param handlerType the handler type to search handler methods on
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
* @return the selected methods, or an empty set
*/
public static Set<Method> selectMethods(final Class<?> handlerType, final MethodFilter handlerMethodFilter) {
final Set<Method> handlerMethods = new LinkedHashSet<Method>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(handlerType)) {
handlerTypes.add(handlerType);
specificHandlerType = handlerType;
}
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (handlerMethodFilter.matches(specificMethod) &&
(bridgedMethod == specificMethod || !handlerMethodFilter.matches(bridgedMethod))) {
handlerMethods.add(specificMethod);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return handlerMethods;
}
}

View File

@ -26,21 +26,20 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.messaging.Message;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.HandlerMethod;
/**
* Invokes the handler method for a given message after resolving
* its method argument values through registered {@link MessageArgumentResolver}s.
* its method argument values through registered {@link HandlerMethodArgumentResolver}s.
* <p>
* Use {@link #setMessageMethodArgumentResolvers(MessageArgumentResolverComposite)}
* Use {@link #setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}
* to customize the list of argument resolvers.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class InvocableMessageHandlerMethod extends HandlerMethod {
public class InvocableHandlerMethod extends HandlerMethod {
private MessageArgumentResolverComposite argumentResolvers = new MessageArgumentResolverComposite();
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
@ -48,14 +47,14 @@ public class InvocableMessageHandlerMethod extends HandlerMethod {
/**
* Create an instance from a {@code HandlerMethod}.
*/
public InvocableMessageHandlerMethod(HandlerMethod handlerMethod) {
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
}
/**
* Create an instance from a bean instance and a method.
*/
public InvocableMessageHandlerMethod(Object bean, Method method) {
public InvocableHandlerMethod(Object bean, Method method) {
super(bean, method);
}
@ -68,17 +67,17 @@ public class InvocableMessageHandlerMethod extends HandlerMethod {
* @param parameterTypes the method parameter types
* @throws NoSuchMethodException when the method cannot be found
*/
public InvocableMessageHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
throws NoSuchMethodException {
super(bean, methodName, parameterTypes);
}
/**
* Set {@link MessageArgumentResolver}s to use to use for resolving method
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method
* argument values.
*/
public void setMessageMethodArgumentResolvers(MessageArgumentResolverComposite argumentResolvers) {
public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
this.argumentResolvers = argumentResolvers;
}

View File

@ -47,6 +47,7 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
public static final String MESSAGE_TYPE = "messageType";
// TODO
public static final String PROTOCOL_MESSAGE_TYPE = "protocolMessageType";
public static final String SESSION_ID = "sessionId";

View File

@ -15,6 +15,8 @@
*/
package org.springframework.messaging.simp;
import java.util.Arrays;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
@ -75,11 +77,9 @@ public class SimpMessagingTemplate extends AbstractMessageSendingTemplate<String
protected <P> Message<P> addDestinationToMessage(Message<P> message, String destination) {
Assert.notNull(destination, "destination is required");
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headers.copyHeaders(message.getHeaders());
headers.setDestination(destination);
message = MessageBuilder.withPayload(message.getPayload()).copyHeaders(headers.toMap()).build();
return message;
return MessageBuilder.fromMessage(message)
.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE, SimpMessageType.MESSAGE)
.setHeader(SimpMessageHeaderAccessor.DESTINATIONS, Arrays.asList(destination)).build();
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2002-2013 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.simp.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReplyToUser {
/**
* The destination for a message based on the return value of a method.
*/
String[] value() default {};
}

View File

@ -0,0 +1,5 @@
/**
* Annotations and support classes for handling messages from simple messaging
* protocols (like STOMP).
*/
package org.springframework.messaging.simp.annotation;

View File

@ -1,128 +0,0 @@
/*
* Copyright 2002-2013 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.simp.annotation.support;
import java.security.Principal;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.handler.annotation.ReplyTo;
import org.springframework.messaging.handler.method.MessageReturnValueHandler;
import org.springframework.messaging.handler.method.MissingSessionUserException;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.util.Assert;
/**
* Expects return values to be either a {@link Message} or the payload of a message to be
* converted and sent on a {@link MessageChannel}.
*
* <p>This {@link MessageReturnValueHandler} should be ordered last as it supports all
* return value types.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class DefaultMessageReturnValueHandler implements MessageReturnValueHandler {
private MessageChannel inboundChannel;
private MessageChannel outboundChannel;
private final MessageConverter converter;
public DefaultMessageReturnValueHandler(MessageChannel inboundChannel, MessageChannel outboundChannel,
MessageConverter<?> converter) {
Assert.notNull(inboundChannel, "inboundChannel is required");
Assert.notNull(outboundChannel, "outboundChannel is required");
Assert.notNull(converter, "converter is required");
this.inboundChannel = inboundChannel;
this.outboundChannel = outboundChannel;
this.converter = converter;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
throws Exception {
if (returnValue == null) {
return;
}
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(message);
Message<?> returnMessage = (returnValue instanceof Message) ? (Message<?>) returnValue : null;
Object returnPayload = (returnMessage != null) ? returnMessage.getPayload() : returnValue;
SimpMessageHeaderAccessor returnHeaders = (returnMessage != null) ?
SimpMessageHeaderAccessor.wrap(returnMessage) : SimpMessageHeaderAccessor.create();
returnHeaders.setSessionId(inputHeaders.getSessionId());
returnHeaders.setSubscriptionId(inputHeaders.getSubscriptionId());
String destination = getDestination(message, returnType, inputHeaders, returnHeaders);
returnHeaders.setDestination(destination);
returnMessage = this.converter.toMessage(returnPayload);
returnMessage = MessageBuilder.fromMessage(returnMessage).copyHeaders(returnHeaders.toMap()).build();
if (destination.startsWith("/user/")) {
this.inboundChannel.send(returnMessage);
}
else {
this.outboundChannel.send(returnMessage);
}
}
protected String getDestination(Message<?> inputMessage, MethodParameter returnType,
SimpMessageHeaderAccessor inputHeaders, SimpMessageHeaderAccessor returnHeaders) {
ReplyTo annot = returnType.getMethodAnnotation(ReplyTo.class);
if (returnHeaders.getDestination() != null) {
return returnHeaders.getDestination();
}
else if (annot != null) {
Principal user = inputHeaders.getUser();
if (user == null) {
throw new MissingSessionUserException(inputMessage);
}
return "/user/" + user.getName() + annot.value();
}
else if (inputHeaders.getDestination() != null) {
return inputHeaders.getDestination();
}
else {
return null;
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.messaging.handler.method;
package org.springframework.messaging.simp.annotation.support;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;

View File

@ -20,8 +20,7 @@ import java.security.Principal;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.method.MessageArgumentResolver;
import org.springframework.messaging.handler.method.MissingSessionUserException;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
@ -29,7 +28,7 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class PrincipalMessageArgumentResolver implements MessageArgumentResolver {
public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override

View File

@ -0,0 +1,122 @@
/*
* Copyright 2002-2013 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.simp.annotation.support;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.messaging.handler.annotation.ReplyTo;
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.annotation.ReplyToUser;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
/**
* A {@link HandlerMethodReturnValueHandler} for replying to destinations specified in a
* {@link ReplyTo} or {@link ReplyToUser} method-level annotations.
* <p>
* The value returned from the method is converted, and turned to a {@link Message} and
* sent through the provided {@link MessageChannel}. The
* message is then enriched with the sessionId of the input message as well as the
* destination from the annotation(s). If multiple destinations are specified, a copy of
* the message is sent to each destination.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ReplyToMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private final MessageSendingOperations<String> messagingTemplate;
public ReplyToMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
Assert.notNull(messagingTemplate, "messagingTemplate is required");
this.messagingTemplate = messagingTemplate;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return ((returnType.getMethodAnnotation(ReplyTo.class) != null)
|| (returnType.getMethodAnnotation(ReplyToUser.class) != null));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> inputMessage)
throws Exception {
if (returnValue == null) {
return;
}
ReplyTo replyTo = returnType.getMethodAnnotation(ReplyTo.class);
ReplyToUser replyToUser = returnType.getMethodAnnotation(ReplyToUser.class);
List<String> destinations = new ArrayList<String>();
if (replyTo != null) {
destinations.addAll(Arrays.asList(replyTo.value()));
}
if (replyToUser != null) {
Principal user = getUser(inputMessage);
for (String destination : replyToUser.value()) {
destinations.add("/user/" + user.getName() + destination);
}
}
MessagePostProcessor postProcessor = new SessionIdHeaderPostProcessor(inputMessage);
for (String destination : destinations) {
this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
}
}
private Principal getUser(Message<?> inputMessage) {
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(inputMessage);
Principal user = inputHeaders.getUser();
if (user == null) {
throw new MissingSessionUserException(inputMessage);
}
return user;
}
private final class SessionIdHeaderPostProcessor implements MessagePostProcessor {
private final Message<?> inputMessage;
public SessionIdHeaderPostProcessor(Message<?> inputMessage) {
this.inputMessage = inputMessage;
}
@Override
public Message<?> postProcessMessage(Message<?> message) {
String headerName = SimpMessageHeaderAccessor.SESSION_ID;
String sessionId = (String) this.inputMessage.getHeaders().get(headerName);
return MessageBuilder.fromMessage(message).setHeader(headerName, sessionId).build();
}
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2002-2013 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.simp.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.core.MessageSendingOperations;
import org.springframework.messaging.handler.annotation.ReplyTo;
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.annotation.ReplyToUser;
import org.springframework.messaging.simp.annotation.SubscribeEvent;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
/**
* A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription. It
* supports methods annotated with {@link SubscribeEvent} that do not also annotated with
* neither {@link ReplyTo} nor {@link ReplyToUser}.
*
* <p>The value returned from the method is converted, and turned to a {@link Message} and
* then enriched with the sessionId, subscriptionId, and destination of the input message.
* The message is then sent directly back to the connected client.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private final MessageSendingOperations<String> messagingTemplate;
public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
Assert.notNull(messagingTemplate, "messagingTemplate is required");
this.messagingTemplate = messagingTemplate;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return ((returnType.getMethodAnnotation(SubscribeEvent.class) != null)
&& (returnType.getMethodAnnotation(ReplyTo.class) == null)
&& (returnType.getMethodAnnotation(ReplyToUser.class) == null));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
throws Exception {
if (returnValue == null) {
return;
}
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(message);
String destination = inputHeaders.getDestination();
Assert.state(inputHeaders.getSubscriptionId() != null,
"No subsriptiondId in input message. Add @ReplyTo or @ReplyToUser to method: "
+ returnType.getMethod());
MessagePostProcessor postProcessor = new InputHeaderCopyingPostProcessor(inputHeaders);
this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
}
private final class InputHeaderCopyingPostProcessor implements MessagePostProcessor {
private final SimpMessageHeaderAccessor inputHeaders;
public InputHeaderCopyingPostProcessor(SimpMessageHeaderAccessor inputHeaders) {
this.inputHeaders = inputHeaders;
}
@Override
public Message<?> postProcessMessage(Message<?> message) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
return MessageBuilder.fromMessage(message)
.setHeader(SimpMessageHeaderAccessor.SESSION_ID, this.inputHeaders.getSessionId())
.setHeader(SimpMessageHeaderAccessor.SUBSCRIPTION_ID, this.inputHeaders.getSubscriptionId())
.copyHeaders(headers.toMap()).build();
}
}
}

View File

@ -38,24 +38,26 @@ import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.support.MessageBodyArgumentResolver;
import org.springframework.messaging.handler.annotation.support.MessageExceptionHandlerMethodResolver;
import org.springframework.messaging.handler.method.InvocableMessageHandlerMethod;
import org.springframework.messaging.handler.method.MessageArgumentResolverComposite;
import org.springframework.messaging.handler.method.MessageReturnValueHandlerComposite;
import org.springframework.messaging.handler.annotation.support.ExceptionHandlerMethodResolver;
import org.springframework.messaging.handler.annotation.support.MessageBodyMethodArgumentResolver;
import org.springframework.messaging.handler.method.HandlerMethod;
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolverComposite;
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandlerComposite;
import org.springframework.messaging.handler.method.HandlerMethodSelector;
import org.springframework.messaging.handler.method.InvocableHandlerMethod;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeEvent;
import org.springframework.messaging.simp.annotation.UnsubscribeEvent;
import org.springframework.messaging.simp.annotation.support.DefaultMessageReturnValueHandler;
import org.springframework.messaging.simp.annotation.support.PrincipalMessageArgumentResolver;
import org.springframework.messaging.simp.annotation.support.PrincipalMethodArgumentResolver;
import org.springframework.messaging.simp.annotation.support.ReplyToMethodReturnValueHandler;
import org.springframework.messaging.simp.annotation.support.SubscriptionMethodReturnValueHandler;
import org.springframework.messaging.support.converter.MessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
/**
@ -80,12 +82,12 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
private Map<MappingInfo, HandlerMethod> unsubscribeMethods = new HashMap<MappingInfo, HandlerMethod>();
private final Map<Class<?>, MessageExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<Class<?>, MessageExceptionHandlerMethodResolver>(64);
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
new ConcurrentHashMap<Class<?>, ExceptionHandlerMethodResolver>(64);
private MessageArgumentResolverComposite argumentResolvers = new MessageArgumentResolverComposite();
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
private MessageReturnValueHandlerComposite returnValueHandlers = new MessageReturnValueHandlerComposite();
private HandlerMethodReturnValueHandlerComposite returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
/**
@ -116,11 +118,17 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
initHandlerMethods();
this.argumentResolvers.addResolver(new PrincipalMessageArgumentResolver());
this.argumentResolvers.addResolver(new MessageBodyArgumentResolver(this.messageConverter));
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
this.argumentResolvers.addResolver(new MessageBodyMethodArgumentResolver(this.messageConverter));
this.returnValueHandlers.addHandler(new DefaultMessageReturnValueHandler(
this.inboundChannel, this.outboundChannel, this.messageConverter));
SimpMessagingTemplate inboundMessagingTemplate = new SimpMessagingTemplate(this.inboundChannel);
inboundMessagingTemplate.setConverter(this.messageConverter);
SimpMessagingTemplate outboundMessagingTemplate = new SimpMessagingTemplate(this.outboundChannel);
outboundMessagingTemplate.setConverter(this.messageConverter);
this.returnValueHandlers.addHandler(new ReplyToMethodReturnValueHandler(inboundMessagingTemplate));
this.returnValueHandlers.addHandler(new SubscriptionMethodReturnValueHandler(outboundMessagingTemplate));
}
protected void initHandlerMethods() {
@ -213,8 +221,7 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
HandlerMethod handlerMethod = match.createWithResolvedBean();
// TODO: avoid re-creating invocableHandlerMethod
InvocableMessageHandlerMethod invocableHandlerMethod = new InvocableMessageHandlerMethod(handlerMethod);
InvocableHandlerMethod invocableHandlerMethod = new InvocableHandlerMethod(handlerMethod);
invocableHandlerMethod.setMessageMethodArgumentResolvers(this.argumentResolvers);
try {
@ -237,11 +244,11 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
private void invokeExceptionHandler(Message<?> message, HandlerMethod handlerMethod, Exception ex) {
InvocableMessageHandlerMethod exceptionHandlerMethod;
InvocableHandlerMethod exceptionHandlerMethod;
Class<?> beanType = handlerMethod.getBeanType();
MessageExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
if (resolver == null) {
resolver = new MessageExceptionHandlerMethodResolver(beanType);
resolver = new ExceptionHandlerMethodResolver(beanType);
this.exceptionHandlerCache.put(beanType, resolver);
}
@ -251,7 +258,7 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
return;
}
exceptionHandlerMethod = new InvocableMessageHandlerMethod(handlerMethod.getBean(), method);
exceptionHandlerMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
exceptionHandlerMethod.setMessageMethodArgumentResolvers(this.argumentResolvers);
try {

View File

@ -34,7 +34,7 @@ import org.springframework.util.StringUtils;
* Supports destinations prefixed with "/user/{username}" and resolves them into a
* destination to which the user is currently subscribed by appending the user session id.
* For example a destination such as "/user/john/queue/trade-confirmation" would resolve
* to "/trade-confirmation/i9oqdfzo" if "i9oqdfzo" is the user's session id.
* to "/queue/trade-confirmation/i9oqdfzo" if "i9oqdfzo" is the user's session id.
*
* @author Rossen Stoyanchev
* @since 4.0

View File

@ -18,8 +18,8 @@ package org.springframework.messaging.support.channel;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel;
@ -31,28 +31,28 @@ import org.springframework.messaging.SubscribableChannel;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class TaskExecutorSubscribableChannel extends AbstractSubscribableChannel {
public class ExecutorSubscribableChannel extends AbstractSubscribableChannel {
private final TaskExecutor executor;
private final Executor executor;
private final Set<MessageHandler> handlers = new CopyOnWriteArraySet<MessageHandler>();
/**
* Create a new {@link TaskExecutorSubscribableChannel} instance where messages will be sent
* Create a new {@link ExecutorSubscribableChannel} instance where messages will be sent
* in the callers thread.
*/
public TaskExecutorSubscribableChannel() {
public ExecutorSubscribableChannel() {
this(null);
}
/**
* Create a new {@link TaskExecutorSubscribableChannel} instance where messages will be sent
* Create a new {@link ExecutorSubscribableChannel} instance where messages will be sent
* via the specified executor.
* @param executor the executor used to send the message or {@code null} to execute in
* the callers thread.
*/
public TaskExecutorSubscribableChannel(TaskExecutor executor) {
public ExecutorSubscribableChannel(Executor executor) {
this.executor = executor;
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2002-2013 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.annotation.support;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.BindException;
import java.net.SocketException;
import org.junit.Test;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import static org.junit.Assert.*;
/**
* Test fixture for {@link ExceptionHandlerMethodResolver} tests.
*
* @author Rossen Stoyanchev
*/
public class ExceptionHandlerMethodResolverTests {
@Test
public void resolveMethodFromAnnotation() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(ExceptionController.class);
IOException exception = new IOException();
assertEquals("handleIOException", resolver.resolveMethod(exception).getName());
}
@Test
public void resolveMethodFromArgument() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(ExceptionController.class);
IllegalArgumentException exception = new IllegalArgumentException();
assertEquals("handleIllegalArgumentException", resolver.resolveMethod(exception).getName());
}
@Test
public void resolveMethodExceptionSubType() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(ExceptionController.class);
IOException ioException = new FileNotFoundException();
assertEquals("handleIOException", resolver.resolveMethod(ioException).getName());
SocketException bindException = new BindException();
assertEquals("handleSocketException", resolver.resolveMethod(bindException).getName());
}
@Test
public void resolveMethodBestMatch() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(ExceptionController.class);
SocketException exception = new SocketException();
assertEquals("handleSocketException", resolver.resolveMethod(exception).getName());
}
@Test
public void resolveMethodNoMatch() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(ExceptionController.class);
Exception exception = new Exception();
assertNull("1st lookup", resolver.resolveMethod(exception));
assertNull("2nd lookup from cache", resolver.resolveMethod(exception));
}
@Test
public void resolveMethodInherited() {
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(InheritedController.class);
IOException exception = new IOException();
assertEquals("handleIOException", resolver.resolveMethod(exception).getName());
}
@Test(expected = IllegalStateException.class)
public void ambiguousExceptionMapping() {
new ExceptionHandlerMethodResolver(AmbiguousController.class);
}
@Test(expected = IllegalArgumentException.class)
public void noExceptionMapping() {
new ExceptionHandlerMethodResolver(NoExceptionController.class);
}
@Controller
static class ExceptionController {
public void handle() {}
@MessageExceptionHandler(IOException.class)
public void handleIOException() {
}
@MessageExceptionHandler(SocketException.class)
public void handleSocketException() {
}
@MessageExceptionHandler
public void handleIllegalArgumentException(IllegalArgumentException exception) {
}
}
@Controller
static class InheritedController extends ExceptionController {
@Override
public void handleIOException() {
}
}
@Controller
static class AmbiguousController {
public void handle() {}
@MessageExceptionHandler({BindException.class, IllegalArgumentException.class})
public String handle1(Exception ex) throws IOException {
return ClassUtils.getShortName(ex.getClass());
}
@MessageExceptionHandler
public String handle2(IllegalArgumentException ex) {
return ClassUtils.getShortName(ex.getClass());
}
}
@Controller
static class NoExceptionController {
@MessageExceptionHandler
public void handle() {
}
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2002-2013 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.simp.annotation.support;
import java.lang.reflect.Method;
import java.security.Principal;
import javax.security.auth.Subject;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.ReplyTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.ReplyToUser;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.converter.MessageConverter;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
* Test fixture for {@link ReplyToMethodReturnValueHandlerTests}.
*
* @author Rossen Stoyanchev
*/
public class ReplyToMethodReturnValueHandlerTests {
private static final String payloadContent = "payload";
private ReplyToMethodReturnValueHandler handler;
@Mock private MessageChannel messageChannel;
@Captor ArgumentCaptor<Message<?>> messageCaptor;
@Mock private MessageConverter messageConverter;
private MethodParameter replyToReturnType;
private MethodParameter replyToUserReturnType;
private MethodParameter missingReplyToReturnType;
@SuppressWarnings("unchecked")
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
Message<String> message = MessageBuilder.withPayload(payloadContent).build();
when(this.messageConverter.toMessage(payloadContent)).thenReturn(message);
SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel);
messagingTemplate.setConverter(this.messageConverter);
this.handler = new ReplyToMethodReturnValueHandler(messagingTemplate);
Method method = this.getClass().getDeclaredMethod("handleAndReplyTo");
this.replyToReturnType = new MethodParameter(method, -1);
method = this.getClass().getDeclaredMethod("handleAndReplyToUser");
this.replyToUserReturnType = new MethodParameter(method, -1);
method = this.getClass().getDeclaredMethod("handleWithMissingReplyTo");
this.missingReplyToReturnType = new MethodParameter(method, -1);
}
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(this.replyToReturnType));
assertTrue(this.handler.supportsReturnType(this.replyToUserReturnType));
assertFalse(this.handler.supportsReturnType(this.missingReplyToReturnType));
}
@Test
public void replyToMethod() throws Exception {
when(this.messageChannel.send(any(Message.class))).thenReturn(true);
String sessionId = "sess1";
Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/dest", null);
this.handler.handleReturnValue(payloadContent, this.replyToReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
Message<?> message = this.messageCaptor.getAllValues().get(0);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
assertEquals(sessionId, headers.getSessionId());
assertNull(headers.getSubscriptionId());
assertEquals("/dest1", headers.getDestination());
message = this.messageCaptor.getAllValues().get(1);
headers = SimpMessageHeaderAccessor.wrap(message);
assertEquals(sessionId, headers.getSessionId());
assertNull(headers.getSubscriptionId());
assertEquals("/dest2", headers.getDestination());
}
@Test
public void replyToUserMethod() throws Exception {
when(this.messageChannel.send(any(Message.class))).thenReturn(true);
String sessionId = "sess1";
TestUser user = new TestUser();
Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/dest", user);
this.handler.handleReturnValue(payloadContent, this.replyToUserReturnType, inputMessage);
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
Message<?> message = this.messageCaptor.getAllValues().get(0);
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
assertEquals(sessionId, headers.getSessionId());
assertNull(headers.getSubscriptionId());
assertEquals("/user/" + user.getName() + "/dest1", headers.getDestination());
message = this.messageCaptor.getAllValues().get(1);
headers = SimpMessageHeaderAccessor.wrap(message);
assertEquals(sessionId, headers.getSessionId());
assertNull(headers.getSubscriptionId());
assertEquals("/user/" + user.getName() + "/dest2", headers.getDestination());
}
private Message<?> createInputMessage(String sessId, String subsId, String dest, Principal principal) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId(sessId);
headers.setSubscriptionId(subsId);
headers.setDestination(dest);
headers.setUser(principal);
return MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build();
}
private static class TestUser implements Principal {
public String getName() {
return "joe";
}
public boolean implies(Subject subject) {
return false;
}
}
@MessageMapping("/handle") // not needed for the tests but here for completeness
public String handleWithMissingReplyTo() {
return payloadContent;
}
@MessageMapping("/handle") // not needed for the tests but here for completeness
@ReplyTo({"/dest1", "/dest2"})
public String handleAndReplyTo() {
return payloadContent;
}
@MessageMapping("/handle") // not needed for the tests but here for completeness
@ReplyToUser({"/dest1", "/dest2"})
public String handleAndReplyToUser() {
return payloadContent;
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright 2002-2013 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.simp.annotation.support;
import java.lang.reflect.Method;
import java.security.Principal;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.ReplyTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeEvent;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.converter.MessageConverter;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
* Test fixture for {@link SubscriptionMethodReturnValueHandler}.
*
* @author Rossen Stoyanchev
*/
public class SubscriptionMethodReturnValueHandlerTests {
private static final String payloadContent = "payload";
private SubscriptionMethodReturnValueHandler handler;
@Mock private MessageChannel messageChannel;
@Captor ArgumentCaptor<Message<?>> messageCaptor;
@Mock private MessageConverter messageConverter;
private MethodParameter subscribeEventReturnType;
private MethodParameter subscribeEventReplyToReturnType;
private MethodParameter messageMappingReturnType;
@SuppressWarnings("unchecked")
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
Message<String> message = MessageBuilder.withPayload(payloadContent).build();
when(this.messageConverter.toMessage(payloadContent)).thenReturn(message);
SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel);
messagingTemplate.setConverter(this.messageConverter);
this.handler = new SubscriptionMethodReturnValueHandler(messagingTemplate);
Method method = this.getClass().getDeclaredMethod("getData");
this.subscribeEventReturnType = new MethodParameter(method, -1);
method = this.getClass().getDeclaredMethod("getDataAndReplyTo");
this.subscribeEventReplyToReturnType = new MethodParameter(method, -1);
method = this.getClass().getDeclaredMethod("handle");
this.messageMappingReturnType = new MethodParameter(method, -1);
}
@Test
public void supportsReturnType() throws Exception {
assertTrue(this.handler.supportsReturnType(this.subscribeEventReturnType));
assertFalse(this.handler.supportsReturnType(this.subscribeEventReplyToReturnType));
assertFalse(this.handler.supportsReturnType(this.messageMappingReturnType));
}
@Test
public void subscribeEventMethod() throws Exception {
when(this.messageChannel.send(any(Message.class))).thenReturn(true);
String sessionId = "sess1";
String subscriptionId = "subs1";
String destination = "/dest";
Message<?> inputMessage = createInputMessage(sessionId, subscriptionId, destination, null);
this.handler.handleReturnValue(payloadContent, this.subscribeEventReturnType, inputMessage);
verify(this.messageChannel).send(this.messageCaptor.capture());
assertNotNull(this.messageCaptor.getValue());
Message<?> message = this.messageCaptor.getValue();
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
assertEquals("sessionId should always be copied", sessionId, headers.getSessionId());
assertEquals(subscriptionId, headers.getSubscriptionId());
assertEquals(destination, headers.getDestination());
}
private Message<?> createInputMessage(String sessId, String subsId, String dest, Principal principal) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId(sessId);
headers.setSubscriptionId(subsId);
headers.setDestination(dest);
headers.setUser(principal);
return MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build();
}
@SubscribeEvent("/data") // not needed for the tests but here for completeness
private String getData() {
return payloadContent;
}
@SubscribeEvent("/data") // not needed for the tests but here for completeness
@ReplyTo("/replyToDest")
private String getDataAndReplyTo() {
return payloadContent;
}
@MessageMapping("/handle") // not needed for the tests but here for completeness
public String handle() {
return payloadContent;
}
}

View File

@ -36,7 +36,7 @@ import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.*;
/**
* Tests for {@link TaskExecutorSubscribableChannel}.
* Tests for {@link ExecutorSubscribableChannel}.
*
* @author Phillip Webb
*/
@ -46,7 +46,7 @@ public class PublishSubscibeChannelTests {
public ExpectedException thrown = ExpectedException.none();
private TaskExecutorSubscribableChannel channel = new TaskExecutorSubscribableChannel();
private ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel();
@Mock
private MessageHandler handler;
@ -71,14 +71,6 @@ public class PublishSubscibeChannelTests {
this.channel.send(null);
}
@Test
public void payloadMustNotBeNull() throws Exception {
Message<?> message = mock(Message.class);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Message payload must not be null");
this.channel.send(message);
}
@Test
public void sendWithoutExecutor() {
this.channel.subscribe(this.handler);
@ -89,7 +81,7 @@ public class PublishSubscibeChannelTests {
@Test
public void sendWithExecutor() throws Exception {
TaskExecutor executor = mock(TaskExecutor.class);
this.channel = new TaskExecutorSubscribableChannel(executor);
this.channel = new ExecutorSubscribableChannel(executor);
this.channel.subscribe(this.handler);
this.channel.send(this.message);
verify(executor).execute(this.runnableCaptor.capture());

View File

@ -206,7 +206,7 @@ public class HandlerMethod {
* @return the annotation, or {@code null} if none found
*/
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return AnnotationUtils.findAnnotation(this.method, annotationType);
return AnnotationUtils.findAnnotation(this.bridgedMethod, annotationType);
}
/**

View File

@ -5,7 +5,7 @@
* 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
* 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,
@ -32,12 +32,13 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.HandlerMethodSelector;
/**
* Discovers {@linkplain ExceptionHandler @ExceptionHandler} methods in a given class
* type, including all super types, and helps to resolve an Exception to the method
* its mapped to. Exception mappings are defined through {@code @ExceptionHandler}
* annotation or by looking at the signature of an {@code @ExceptionHandler} method.
*
* Discovers annotated exception handling methods in a given class type, including all
* super types, and helps to resolve an Exception to a method that can handle it. The
* exception types supported by a given method can also be discovered from the method
* signature.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@ -56,17 +57,13 @@ public class ExceptionHandlerMethodResolver {
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : HandlerMethodSelector.selectMethods(handlerType, getExceptionHandlerMethods())) {
for (Method method : HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
protected MethodFilter getExceptionHandlerMethods() {
return EXCEPTION_HANDLER_METHODS;
}
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation
* first and as a fall-back from the method signature.
@ -91,8 +88,8 @@ public class ExceptionHandlerMethodResolver {
}
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler annotation = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
result.addAll(Arrays.asList(annotation.value()));
ExceptionHandler annot = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
result.addAll(Arrays.asList(annot.value()));
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
@ -146,9 +143,8 @@ public class ExceptionHandlerMethodResolver {
}
}
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
/** A filter for selecting annotated exception handling methods. */
public final static MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
@Override