parent
73a794336c
commit
a112557dc4
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -157,7 +157,7 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
|
|||
*/
|
||||
protected String getDefaultResponseDestination() {
|
||||
Method specificMethod = getMostSpecificMethod();
|
||||
SendTo ann = AnnotationUtils.getAnnotation(specificMethod, SendTo.class);
|
||||
SendTo ann = getSendTo(specificMethod);
|
||||
if (ann != null) {
|
||||
Object[] destinations = ann.value();
|
||||
if (destinations.length != 1) {
|
||||
|
|
@ -169,6 +169,16 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
|
|||
return null;
|
||||
}
|
||||
|
||||
private SendTo getSendTo(Method specificMethod) {
|
||||
SendTo ann = AnnotationUtils.getAnnotation(specificMethod, SendTo.class);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
else {
|
||||
return AnnotationUtils.getAnnotation(specificMethod.getDeclaringClass(), SendTo.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the specified value if possible.
|
||||
* @see ConfigurableBeanFactory#resolveEmbeddedValue
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -266,7 +266,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
|
||||
MessagingMessageListenerAdapter listener = createInstance(this.factory,
|
||||
getListenerMethod(methodName, String.class), container);
|
||||
processAndReplyWithSendTo(listener, false);
|
||||
processAndReplyWithSendTo(listener, "replyDestination", false);
|
||||
assertListenerMethodInvocation(sample, methodName);
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
container.setReplyPubSubDomain(false);
|
||||
MessagingMessageListenerAdapter listener = createInstance(this.factory,
|
||||
getListenerMethod(methodName, String.class), container);
|
||||
processAndReplyWithSendTo(listener, false);
|
||||
processAndReplyWithSendTo(listener, "replyDestination", false);
|
||||
assertListenerMethodInvocation(sample, methodName);
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
container.setPubSubDomain(true);
|
||||
MessagingMessageListenerAdapter listener = createInstance(this.factory,
|
||||
getListenerMethod(methodName, String.class), container);
|
||||
processAndReplyWithSendTo(listener, true);
|
||||
processAndReplyWithSendTo(listener, "replyDestination", true);
|
||||
assertListenerMethodInvocation(sample, methodName);
|
||||
}
|
||||
|
||||
|
|
@ -300,11 +300,19 @@ public class MethodJmsListenerEndpointTests {
|
|||
container.setReplyPubSubDomain(true);
|
||||
MessagingMessageListenerAdapter listener = createInstance(this.factory,
|
||||
getListenerMethod(methodName, String.class), container);
|
||||
processAndReplyWithSendTo(listener, true);
|
||||
processAndReplyWithSendTo(listener, "replyDestination", true);
|
||||
assertListenerMethodInvocation(sample, methodName);
|
||||
}
|
||||
|
||||
private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener, boolean pubSubDomain) throws JMSException {
|
||||
@Test
|
||||
public void processAndReplyWithDefaultSendTo() throws JMSException {
|
||||
MessagingMessageListenerAdapter listener = createDefaultInstance(String.class);
|
||||
processAndReplyWithSendTo(listener, "defaultReply", false);
|
||||
assertDefaultListenerMethodInvocation();
|
||||
}
|
||||
|
||||
private void processAndReplyWithSendTo(MessagingMessageListenerAdapter listener,
|
||||
String replyDestinationName, boolean pubSubDomain) throws JMSException {
|
||||
String body = "echo text";
|
||||
String correlationId = "link-1234";
|
||||
Destination replyDestination = new Destination() {};
|
||||
|
|
@ -314,7 +322,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
QueueSender queueSender = mock(QueueSender.class);
|
||||
Session session = mock(Session.class);
|
||||
|
||||
given(destinationResolver.resolveDestinationName(session, "replyDestination", pubSubDomain))
|
||||
given(destinationResolver.resolveDestinationName(session, replyDestinationName, pubSubDomain))
|
||||
.willReturn(replyDestination);
|
||||
given(session.createTextMessage(body)).willReturn(reply);
|
||||
given(session.createProducer(replyDestination)).willReturn(queueSender);
|
||||
|
|
@ -324,7 +332,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
inputMessage.setJMSCorrelationID(correlationId);
|
||||
listener.onMessage(inputMessage, session);
|
||||
|
||||
verify(destinationResolver).resolveDestinationName(session, "replyDestination", pubSubDomain);
|
||||
verify(destinationResolver).resolveDestinationName(session, replyDestinationName, pubSubDomain);
|
||||
verify(reply).setJMSCorrelationID(correlationId);
|
||||
verify(queueSender).send(reply);
|
||||
verify(queueSender).close();
|
||||
|
|
@ -470,6 +478,7 @@ public class MethodJmsListenerEndpointTests {
|
|||
}
|
||||
|
||||
|
||||
@SendTo("defaultReply")
|
||||
static class JmsEndpointSampleBean {
|
||||
|
||||
private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
|
||||
|
|
@ -549,6 +558,11 @@ public class MethodJmsListenerEndpointTests {
|
|||
return content;
|
||||
}
|
||||
|
||||
public String processAndReplyWithDefaultSendTo(String content) {
|
||||
invocations.put("processAndReplyWithDefaultSendTo", true);
|
||||
return content;
|
||||
}
|
||||
|
||||
@SendTo("")
|
||||
public String emptySendTo(String content) {
|
||||
invocations.put("emptySendTo", true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -32,10 +32,15 @@ import org.springframework.messaging.Message;
|
|||
* convey the destination to use for the reply. In that case, that destination
|
||||
* should take precedence.
|
||||
*
|
||||
* <p>The annotation may also be placed at class-level if the provider supports
|
||||
* it to indicate that all related methods should use this destination if none
|
||||
* is specified otherwise.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SendTo {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -133,6 +133,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
|
|||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
if (returnType.getMethodAnnotation(SendTo.class) != null ||
|
||||
AnnotationUtils.getAnnotation(returnType.getDeclaringClass(), SendTo.class) != null ||
|
||||
returnType.getMethodAnnotation(SendToUser.class) != null) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -174,7 +175,7 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
|
|||
}
|
||||
}
|
||||
else {
|
||||
SendTo sendTo = returnType.getMethodAnnotation(SendTo.class);
|
||||
SendTo sendTo = getSendTo(returnType);
|
||||
String[] destinations = getTargetDestinations(sendTo, message, this.defaultDestinationPrefix);
|
||||
for (String destination : destinations) {
|
||||
destination = this.placeholderHelper.replacePlaceholders(destination, varResolver);
|
||||
|
|
@ -183,6 +184,16 @@ public class SendToMethodReturnValueHandler implements HandlerMethodReturnValueH
|
|||
}
|
||||
}
|
||||
|
||||
private SendTo getSendTo(MethodParameter returnType) {
|
||||
SendTo sendTo = returnType.getMethodAnnotation(SendTo.class);
|
||||
if (sendTo != null && !ObjectUtils.isEmpty((sendTo.value()))) {
|
||||
return sendTo;
|
||||
}
|
||||
else {
|
||||
return AnnotationUtils.getAnnotation(returnType.getDeclaringClass(), SendTo.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private PlaceholderResolver initVarResolver(MessageHeaders headers) {
|
||||
String name = DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
|
@ -88,6 +88,9 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
private MethodParameter sendToUserDefaultDestReturnType;
|
||||
private MethodParameter sendToUserSingleSessionDefaultDestReturnType;
|
||||
private MethodParameter jsonViewReturnType;
|
||||
private MethodParameter defaultNoAnnotation;
|
||||
private MethodParameter defaultEmptyAnnotation;
|
||||
private MethodParameter defaultOverrideAnnotation;
|
||||
|
||||
|
||||
@Before
|
||||
|
|
@ -129,6 +132,15 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
|
||||
method = this.getClass().getDeclaredMethod("handleAndSendToJsonView");
|
||||
this.jsonViewReturnType = new SynthesizingMethodParameter(method, -1);
|
||||
|
||||
method = TestBean.class.getDeclaredMethod("handleNoAnnotation");
|
||||
this.defaultNoAnnotation = new SynthesizingMethodParameter(method, -1);
|
||||
|
||||
method = TestBean.class.getDeclaredMethod("handleAndSendToDefaultDestination");
|
||||
this.defaultEmptyAnnotation = new SynthesizingMethodParameter(method, -1);
|
||||
|
||||
method = TestBean.class.getDeclaredMethod("handleAndSendToOverride");
|
||||
this.defaultOverrideAnnotation = new SynthesizingMethodParameter(method, -1);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -138,23 +150,22 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
assertTrue(this.handler.supportsReturnType(this.sendToUserReturnType));
|
||||
assertFalse(this.handler.supportsReturnType(this.noAnnotationsReturnType));
|
||||
assertTrue(this.handlerAnnotationNotRequired.supportsReturnType(this.noAnnotationsReturnType));
|
||||
|
||||
assertTrue(this.handler.supportsReturnType(this.defaultNoAnnotation));
|
||||
assertTrue(this.handler.supportsReturnType(this.defaultEmptyAnnotation));
|
||||
assertTrue(this.handler.supportsReturnType(this.defaultOverrideAnnotation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToNoAnnotations() throws Exception {
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
Message<?> inputMessage = createInputMessage("sess1", "sub1", "/app", "/dest", null);
|
||||
String sessionId = "sess1";
|
||||
Message<?> inputMessage = createInputMessage(sessionId, "sub1", "/app", "/dest", null);
|
||||
this.handler.handleReturnValue(PAYLOAD, this.noAnnotationsReturnType, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
|
||||
|
||||
SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
|
||||
assertEquals("sess1", accessor.getSessionId());
|
||||
assertEquals("/topic/dest", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.noAnnotationsReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
assertResponse(this.noAnnotationsReturnType, sessionId, 0, "/topic/dest");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -166,20 +177,8 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
this.handler.handleReturnValue(PAYLOAD, this.sendToReturnType, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
|
||||
|
||||
SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
|
||||
assertEquals(sessionId, accessor.getSessionId());
|
||||
assertEquals("/dest1", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
|
||||
accessor = getCapturedAccessor(1);
|
||||
assertEquals(sessionId, accessor.getSessionId());
|
||||
assertEquals("/dest2", accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
assertResponse(this.sendToReturnType, sessionId, 0, "/dest1");
|
||||
assertResponse(this.sendToReturnType, sessionId, 1, "/dest2");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -191,13 +190,54 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
this.handler.handleReturnValue(PAYLOAD, this.sendToDefaultDestReturnType, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
|
||||
assertResponse(this.sendToDefaultDestReturnType, sessionId, 0, "/topic/dest");
|
||||
}
|
||||
|
||||
SimpMessageHeaderAccessor accessor = getCapturedAccessor(0);
|
||||
@Test
|
||||
public void sendToClassDefaultNoAnnotation() throws Exception {
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
|
||||
this.handler.handleReturnValue(PAYLOAD, this.defaultNoAnnotation, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
|
||||
assertResponse(this.defaultNoAnnotation, sessionId, 0, "/dest-default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToClassDefaultEmptyAnnotation() throws Exception {
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
|
||||
this.handler.handleReturnValue(PAYLOAD, this.defaultEmptyAnnotation, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(1)).send(this.messageCaptor.capture());
|
||||
assertResponse(this.defaultEmptyAnnotation, sessionId, 0, "/dest-default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendToClassDefaultOverride() throws Exception {
|
||||
given(this.messageChannel.send(any(Message.class))).willReturn(true);
|
||||
|
||||
String sessionId = "sess1";
|
||||
Message<?> inputMessage = createInputMessage(sessionId, "sub1", null, null, null);
|
||||
this.handler.handleReturnValue(PAYLOAD, this.defaultOverrideAnnotation, inputMessage);
|
||||
|
||||
verify(this.messageChannel, times(2)).send(this.messageCaptor.capture());
|
||||
assertResponse(this.defaultOverrideAnnotation, sessionId, 0, "/dest3");
|
||||
assertResponse(this.defaultOverrideAnnotation, sessionId, 1, "/dest4");
|
||||
}
|
||||
|
||||
private void assertResponse(MethodParameter methodParameter, String sessionId,
|
||||
int index, String destination) {
|
||||
SimpMessageHeaderAccessor accessor = getCapturedAccessor(index);
|
||||
assertEquals(sessionId, accessor.getSessionId());
|
||||
assertEquals("/topic/dest", accessor.getDestination());
|
||||
assertEquals(destination, accessor.getDestination());
|
||||
assertEquals(MIME_TYPE, accessor.getContentType());
|
||||
assertNull("Subscription id should not be copied", accessor.getSubscriptionId());
|
||||
assertEquals(this.sendToDefaultDestReturnType, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
assertEquals(methodParameter, accessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -497,6 +537,25 @@ public class SendToMethodReturnValueHandlerTests {
|
|||
return payload;
|
||||
}
|
||||
|
||||
@SendTo("/dest-default")
|
||||
private static class TestBean {
|
||||
|
||||
public String handleNoAnnotation() {
|
||||
return PAYLOAD;
|
||||
}
|
||||
|
||||
@SendTo
|
||||
public String handleAndSendToDefaultDestination() {
|
||||
return PAYLOAD;
|
||||
}
|
||||
|
||||
@SendTo({"/dest3", "/dest4"})
|
||||
public String handleAndSendToOverride() {
|
||||
return PAYLOAD;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
private interface MyJacksonView2 {}
|
||||
|
|
|
|||
|
|
@ -2796,6 +2796,9 @@ as follow to automatically send a response:
|
|||
}
|
||||
----
|
||||
|
||||
TIP: If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo`
|
||||
annotation at class-level to share a default reply destination.
|
||||
|
||||
If you need to set additional headers in a transport-independent manner, you could return a
|
||||
`Message` instead, something like:
|
||||
|
||||
|
|
|
|||
|
|
@ -1359,7 +1359,8 @@ The return value from an `@MessageMapping` method is converted with a
|
|||
of a new message that is then sent, by default, to the `"brokerChannel"` with
|
||||
the same destination as the client message but using the prefix `"/topic"` by
|
||||
default. An `@SendTo` message level annotation can be used to specify any
|
||||
other destination instead.
|
||||
other destination instead. It can also be set a class-level to share a common
|
||||
destination.
|
||||
|
||||
An `@SubscribeMapping` annotation can also be used to map subscription requests
|
||||
to `@Controller` methods. It is supported on the method level, but can also be
|
||||
|
|
|
|||
|
|
@ -650,12 +650,20 @@ Spring 4.3 also improves the caching abstraction as follows:
|
|||
* `ConcurrentMapCacheManager` and `ConcurrentMapCache` now support the serialization
|
||||
of cache entries via a new `storeByValue` attribute.
|
||||
|
||||
=== JMS Improvements
|
||||
|
||||
* `@SendTo` can now be specified at class-level to share a common reply destination.
|
||||
|
||||
=== Web Improvements
|
||||
|
||||
* New `@RestControllerAdvice` annotation combines `@ControllerAdvice` with `@ResponseBody`.
|
||||
* `@ResponseStatus` can be used on a controller type is inherited for all method.
|
||||
* `AsyncRestTemplate` supports request interception.
|
||||
|
||||
=== WebSocket Messaging Improvements
|
||||
|
||||
* `@SendTo` can now be specified at class-level to share a common destination.
|
||||
|
||||
=== Testing Improvements
|
||||
|
||||
* The JUnit support in the _Spring TestContext Framework_ now requires JUnit 4.12 or higher.
|
||||
|
|
|
|||
Loading…
Reference in New Issue