AMQP-807: Use method genericReturnType for conv.

JIRA: https://jira.spring.io/browse/AMQP-807

When converting `@RabbitListener` results, use the generic return type
of the method, instead of `object.getClass()` to properly convey
type information in message headers.

Otherwise, `List<Foo>` becomes `List<Map>` on the receiving side, since
the list content type is erased.

* Polishing according PR comments
This commit is contained in:
Gary Russell
2018-03-30 12:03:36 -04:00
committed by Artem Bilan
parent bf5498db80
commit 38f59a2537
14 changed files with 274 additions and 101 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -16,14 +16,18 @@
package org.springframework.amqp.support.converter;
import java.lang.reflect.Type;
import java.util.UUID;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.lang.Nullable;
/**
* Convenient base class for {@link MessageConverter} implementations.
*
* @author Dave Syer
* @author Gary Russell
*
*/
public abstract class AbstractMessageConverter implements MessageConverter {
@@ -50,10 +54,18 @@ public abstract class AbstractMessageConverter implements MessageConverter {
@Override
public final Message toMessage(Object object, MessageProperties messageProperties)
throws MessageConversionException {
return toMessage(object, messageProperties, null);
}
@Override
public final Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType)
throws MessageConversionException {
if (messageProperties == null) {
messageProperties = new MessageProperties();
}
Message message = createMessage(object, messageProperties);
Message message = createMessage(object, messageProperties, genericType);
messageProperties = message.getMessageProperties();
if (this.createMessageIds && messageProperties.getMessageId() == null) {
messageProperties.setMessageId(UUID.randomUUID().toString());
@@ -64,14 +76,23 @@ public abstract class AbstractMessageConverter implements MessageConverter {
/**
* Crate a message from the payload object and message properties provided. The message id will be added to the
* properties if necessary later.
*
* @param object the payload
* @param messageProperties the message properties (headers)
* @param genericType the type to convert from - used to populate type headers.
* @return a message
* @since 2.1
*/
protected Message createMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType) {
return createMessage(object, messageProperties);
}
/**
* Crate a message from the payload object and message properties provided. The message id will be added to the
* properties if necessary later.
* @param object the payload.
* @param messageProperties the message properties (headers).
* @return a message.
*/
protected abstract Message createMessage(Object object, MessageProperties messageProperties);
@Override
public abstract Object fromMessage(Message message) throws MessageConversionException;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Jackson 2 type mapper.
*
* @author Mark Pollack
* @author Sam Nelson
* @author Andreas Asplund
@@ -104,6 +105,11 @@ public class DefaultJackson2JavaTypeMapper extends AbstractJavaTypeMapper
}
}
@Override
public void addTrustedPackages(String... packages) {
setTrustedPackages(packages);
}
@Override
public JavaType toJavaType(MessageProperties properties) {
boolean hasInferredTypeHeader = hasInferredTypeHeader(properties);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -35,15 +35,40 @@ public interface Jackson2JavaTypeMapper extends ClassMapper {
/**
* The precedence for type conversion - inferred from the method parameter or message
* headers. Only applies if both exist.
* @since 1.6
*/
enum TypePrecedence {
INFERRED, TYPE_ID
}
/**
* Set the message properties according to the type.
* @param javaType the type.
* @param properties the properties.
*/
void fromJavaType(JavaType javaType, MessageProperties properties);
/**
* Determine the type from the message properties.
* @param properties the properties.
* @return the type.
*/
JavaType toJavaType(MessageProperties properties);
/**
* Get the type precedence.
* @return the precedence.
* @since 1.6
*/
TypePrecedence getTypePrecedence();
/**
* Add trusted packages.
* @param packages the packages.
* @since 2.1
*/
default void addTrustedPackages(String... packages) {
// no op
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -17,6 +17,7 @@
package org.springframework.amqp.support.converter;
import java.io.IOException;
import java.lang.reflect.Type;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -224,6 +225,14 @@ public class Jackson2JsonMessageConverter extends AbstractJsonMessageConverter i
@Override
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties)
throws MessageConversionException {
return createMessage(objectToConvert, messageProperties, null);
}
@Override
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties, Type genericType)
throws MessageConversionException {
byte[] bytes;
try {
String jsonString = this.jsonObjectMapper
@@ -231,22 +240,19 @@ public class Jackson2JsonMessageConverter extends AbstractJsonMessageConverter i
bytes = jsonString.getBytes(getDefaultCharset());
}
catch (IOException e) {
throw new MessageConversionException(
"Failed to convert Message content", e);
throw new MessageConversionException("Failed to convert Message content", e);
}
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
messageProperties.setContentEncoding(getDefaultCharset());
messageProperties.setContentLength(bytes.length);
if (getClassMapper() == null) {
getJavaTypeMapper().fromJavaType(this.jsonObjectMapper.constructType(objectToConvert.getClass()),
messageProperties);
getJavaTypeMapper().fromJavaType(this.jsonObjectMapper.constructType(
genericType == null ? objectToConvert.getClass() : genericType), messageProperties);
}
else {
getClassMapper().fromClass(objectToConvert.getClass(),
messageProperties);
}
return new Message(bytes, messageProperties);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -16,13 +16,18 @@
package org.springframework.amqp.support.converter;
import java.lang.reflect.Type;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.lang.Nullable;
/**
* Message converter interface.
*
* @author Mark Fisher
* @author Mark Pollack
* @author Gary Russell
*/
public interface MessageConverter {
@@ -35,6 +40,22 @@ public interface MessageConverter {
*/
Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException;
/**
* Convert a Java object to a Message.
* The default implementation calls {@link #toMessage(Object, MessageProperties)}.
* @param object the object to convert
* @param messageProperties The message properties.
* @param genericType the type to use to populate type headers.
* @return the Message
* @throws MessageConversionException in case of conversion failure
* @since 2.1
*/
default Message toMessage(Object object, MessageProperties messageProperties, @Nullable Type genericType)
throws MessageConversionException {
return toMessage(object, messageProperties);
}
/**
* Convert from a Message to a Java object.
* @param message the message to convert

View File

@@ -115,7 +115,7 @@ public class MethodRabbitListenerEndpoint extends AbstractRabbitListenerEndpoint
Assert.state(this.messageHandlerMethodFactory != null,
"Could not create message listener - MessageHandlerMethodFactory not set");
MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
messageListener.setHandlerMethod(configureListenerAdapter(messageListener));
messageListener.setHandlerAdapter(configureListenerAdapter(messageListener));
String replyToAddress = getDefaultReplyToAddress();
if (replyToAddress != null) {
messageListener.setResponseAddress(replyToAddress);

View File

@@ -16,6 +16,7 @@
package org.springframework.amqp.rabbit.listener.adapter;
import java.lang.reflect.Type;
import java.util.Arrays;
import org.apache.commons.logging.Log;
@@ -179,8 +180,7 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* Set a post processor to process the reply immediately before
* {@code Channel#basicPublish()}. Often used to compress the data.
* @param replyPostProcessor the reply post processor.
* @deprecated in favor of
* {@link #setBeforeSendReplyPostProcessors(MessagePostProcessor...)}.
* @deprecated in favor of {@link #setBeforeSendReplyPostProcessors(MessagePostProcessor...)}.
*/
@Deprecated
public void setReplyPostProcessor(MessagePostProcessor replyPostProcessor) {
@@ -271,13 +271,12 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* @param resultArg the result object to handle (never <code>null</code>)
* @param request the original request message
* @param channel the Rabbit channel to operate on (may be <code>null</code>)
* @throws Exception if thrown by Rabbit API methods
* @see #buildMessage
* @see #postProcessResponse
* @see #getReplyToAddress(Message, Object, Object)
* @see #getReplyToAddress(Message, Object, InvocationResult)
* @see #sendResponse
*/
protected void handleResult(Object resultArg, Message request, Channel channel) throws Exception {
protected void handleResult(InvocationResult resultArg, Message request, Channel channel) {
handleResult(resultArg, request, channel, null);
}
@@ -289,21 +288,19 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* @param channel the Rabbit channel to operate on (may be <code>null</code>)
* @param source the source data for the method invocation - e.g.
* {@code o.s.messaging.Message<?>}; may be null
* @throws Exception if thrown by Rabbit API methods
* @see #buildMessage
* @see #postProcessResponse
* @see #getReplyToAddress(Message, Object, Object)
* @see #getReplyToAddress(Message, Object, InvocationResult)
* @see #sendResponse
*/
protected void handleResult(Object resultArg, Message request, Channel channel, Object source) throws Exception {
protected void handleResult(InvocationResult resultArg, Message request, Channel channel, Object source) {
if (channel != null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Listener method returned result [" + resultArg
+ "] - generating response message for it");
}
try {
Object result = resultArg instanceof ResultHolder ? ((ResultHolder) resultArg).result : resultArg;
Message response = buildMessage(channel, result);
Message response = buildMessage(channel, resultArg.getReturnValue(), resultArg.getReturnType());
postProcessResponse(request, response);
Address replyTo = getReplyToAddress(request, source, resultArg);
sendResponse(channel, replyTo, response);
@@ -324,16 +321,17 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
/**
* Build a Rabbit message to be sent as response based on the given result object.
* @param channel the Rabbit Channel to operate on
* @param result the content of the message, as returned from the listener method
* @return the Rabbit <code>Message</code> (never <code>null</code>)
* @throws Exception if thrown by Rabbit API methods
* @param channel the Rabbit Channel to operate on.
* @param result the content of the message, as returned from the listener method.
* @param genericType the generic type to populate type headers.
* @return the Rabbit <code>Message</code> (never <code>null</code>).
* @throws Exception if thrown by Rabbit API methods.
* @see #setMessageConverter
*/
protected Message buildMessage(Channel channel, Object result) throws Exception {
protected Message buildMessage(Channel channel, Object result, Type genericType) throws Exception {
MessageConverter converter = getMessageConverter();
if (converter != null && !(result instanceof Message)) {
return converter.toMessage(result, new MessageProperties());
return converter.toMessage(result, new MessageProperties(), genericType);
}
else {
if (!(result instanceof Message)) {
@@ -351,9 +349,8 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* otherwise to the request message id.
* @param request the original incoming Rabbit message
* @param response the outgoing Rabbit message about to be sent
* @throws Exception if thrown by Rabbit API methods
*/
protected void postProcessResponse(Message request, Message response) throws Exception {
protected void postProcessResponse(Message request, Message response) {
String correlation = request.getMessageProperties().getCorrelationId();
if (correlation == null) {
@@ -376,24 +373,23 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* @param source the source data (e.g. {@code o.s.messaging.Message<?>}).
* @param result the result.
* @return the reply-to Address (never <code>null</code>)
* @throws Exception if thrown by Rabbit API methods
* @throws org.springframework.amqp.AmqpException if no {@link Address} can be determined
* @see #setResponseAddress(String)
* @see #setResponseRoutingKey(String)
* @see org.springframework.amqp.core.Message#getMessageProperties()
* @see org.springframework.amqp.core.MessageProperties#getReplyTo()
*/
protected Address getReplyToAddress(Message request, Object source, Object result) throws Exception {
protected Address getReplyToAddress(Message request, Object source, InvocationResult result) {
Address replyTo = request.getMessageProperties().getReplyToAddress();
if (replyTo == null) {
if (this.responseAddress == null && this.responseExchange != null) {
this.responseAddress = new Address(this.responseExchange, this.responseRoutingKey);
}
if (result instanceof ResultHolder) {
replyTo = evaluateReplyTo(request, source, result, ((ResultHolder) result).sendTo);
if (result.getSendTo() != null) {
replyTo = evaluateReplyTo(request, source, result.getReturnValue(), result.getSendTo());
}
else if (this.responseExpression != null) {
replyTo = evaluateReplyTo(request, source, result, this.responseExpression);
replyTo = evaluateReplyTo(request, source, result.getReturnValue(), this.responseExpression);
}
else if (this.responseAddress == null) {
throw new AmqpException(
@@ -427,10 +423,9 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
* @param channel the Rabbit channel to operate on
* @param replyTo the Rabbit ReplyTo string to use when sending. Currently interpreted to be the routing key.
* @param messageIn the Rabbit message to send
* @throws Exception if thrown by Rabbit API methods
* @see #postProcessResponse(Message, Message)
*/
protected void sendResponse(Channel channel, Address replyTo, Message messageIn) throws Exception {
protected void sendResponse(Channel channel, Address replyTo, Message messageIn) {
Message message = messageIn;
if (this.beforeSendReplyPostProcessors != null) {
for (MessagePostProcessor postProcessor : this.beforeSendReplyPostProcessors) {
@@ -458,30 +453,8 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
*
* @param channel The channel.
* @param response the outgoing Rabbit message about to be sent
* @throws Exception if thrown by Rabbit API methods
*/
protected void postProcessChannel(Channel channel, Message response) throws Exception {
}
/**
* Result holder.
*/
public static final class ResultHolder {
private final Object result;
private final Expression sendTo;
public ResultHolder(Object result, Expression sendTo) {
this.result = result;
this.sendTo = sendTo;
}
@Override
public String toString() {
return this.result.toString();
}
protected void postProcessChannel(Channel channel, Message response) {
}
/**
@@ -495,7 +468,7 @@ public abstract class AbstractAdaptableMessageListener implements MessageListene
private final Object result;
public ReplyExpressionRoot(Message request, Object source, Object result) {
protected ReplyExpressionRoot(Message request, Object source, Object result) {
this.request = request;
this.source = source;
this.result = result;

View File

@@ -51,6 +51,7 @@ import org.springframework.util.Assert;
* Matches must be unambiguous.
*
* @author Gary Russell
*
* @since 1.5
*
*/
@@ -62,13 +63,11 @@ public class DelegatingInvocableHandler {
private final List<InvocableHandlerMethod> handlers;
private final ConcurrentMap<Class<?>, InvocableHandlerMethod> cachedHandlers =
new ConcurrentHashMap<Class<?>, InvocableHandlerMethod>();
private final ConcurrentMap<Class<?>, InvocableHandlerMethod> cachedHandlers = new ConcurrentHashMap<>();
private final InvocableHandlerMethod defaultHandler;
private final Map<InvocableHandlerMethod, Expression> handlerSendTo =
new HashMap<InvocableHandlerMethod, Expression>();
private final Map<InvocableHandlerMethod, Expression> handlerSendTo = new HashMap<>();
private final Object bean;
@@ -85,6 +84,7 @@ public class DelegatingInvocableHandler {
*/
public DelegatingInvocableHandler(List<InvocableHandlerMethod> handlers, Object bean,
BeanExpressionResolver beanExpressionResolver, BeanExpressionContext beanExpressionContext) {
this(handlers, null, bean, beanExpressionResolver, beanExpressionContext);
}
@@ -100,7 +100,8 @@ public class DelegatingInvocableHandler {
public DelegatingInvocableHandler(List<InvocableHandlerMethod> handlers,
@Nullable InvocableHandlerMethod defaultHandler, Object bean, BeanExpressionResolver beanExpressionResolver,
BeanExpressionContext beanExpressionContext) {
this.handlers = new ArrayList<InvocableHandlerMethod>(handlers);
this.handlers = new ArrayList<>(handlers);
this.defaultHandler = defaultHandler;
this.bean = bean;
this.resolver = beanExpressionResolver;
@@ -122,17 +123,17 @@ public class DelegatingInvocableHandler {
* @throws Exception raised if no suitable argument resolver can be found,
* or the method raised an exception.
*/
public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
public InvocationResult invoke(Message<?> message, Object... providedArgs) throws Exception {
Class<? extends Object> payloadClass = message.getPayload().getClass();
InvocableHandlerMethod handler = getHandlerForPayload(payloadClass);
Object result = handler.invoke(message, providedArgs);
if (message.getHeaders().get(AmqpHeaders.REPLY_TO) == null) {
Expression replyTo = this.handlerSendTo.get(handler);
if (replyTo != null) {
result = new AbstractAdaptableMessageListener.ResultHolder(result, replyTo);
return new InvocationResult(result, replyTo, handler.getMethod().getGenericReturnType());
}
}
return result;
return new InvocationResult(result, null, handler.getMethod().getGenericReturnType());
}
/**

View File

@@ -46,9 +46,10 @@ public class HandlerAdapter {
this.delegatingHandler = delegatingHandler;
}
public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
public InvocationResult invoke(Message<?> message, Object... providedArgs) throws Exception {
if (this.invokerHandlerMethod != null) {
return this.invokerHandlerMethod.invoke(message, providedArgs);
return new InvocationResult(this.invokerHandlerMethod.invoke(message, providedArgs),
null, this.invokerHandlerMethod.getMethod().getGenericReturnType());
}
else if (this.delegatingHandler.hasDefaultHandler()) {
// Needed to avoid returning raw Message which matches Object

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2018 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.amqp.rabbit.listener.adapter;
import java.lang.reflect.Type;
import org.springframework.expression.Expression;
/**
* The result of a listener method invocation.
*
* @author Gary Russell
*
* @since 2.1
*/
public final class InvocationResult {
private final Object returnValue;
private final Expression sendTo;
private final Type returnType;
public InvocationResult(Object result, Expression sendTo, Type returnType) {
this.returnValue = result;
this.sendTo = sendTo;
this.returnType = returnType;
}
public Object getReturnValue() {
return this.returnValue;
}
public Expression getSendTo() {
return this.sendTo;
}
public Type getReturnType() {
return this.returnType;
}
@Override
public String toString() {
return "InvocationResult [returnValue=" + this.returnValue
+ (this.sendTo != null ? ", sendTo=" + this.sendTo : "")
+ ", returnType=" + this.returnType + "]";
}
}

View File

@@ -297,7 +297,7 @@ public class MessageListenerAdapter extends AbstractAdaptableMessageListener {
Object[] listenerArguments = buildListenerArguments(convertedMessage);
Object result = invokeListenerMethod(methodName, listenerArguments, message);
if (result != null) {
handleResult(result, message, channel);
handleResult(new InvocationResult(result, null, null), message, channel);
}
else {
logger.trace("No result object given - no result to handle");

View File

@@ -58,7 +58,7 @@ import com.rabbitmq.client.Channel;
*/
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
private HandlerAdapter handlerMethod;
private HandlerAdapter handlerAdapter;
private final MessagingMessageConverterAdapter messagingMessageConverter;
@@ -84,10 +84,10 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
/**
* Set the {@link HandlerAdapter} to use to invoke the method
* processing an incoming {@link org.springframework.amqp.core.Message}.
* @param handlerMethod {@link HandlerAdapter} instance.
* @param handlerAdapter {@link HandlerAdapter} instance.
*/
public void setHandlerMethod(HandlerAdapter handlerMethod) {
this.handlerMethod = handlerMethod;
public void setHandlerAdapter(HandlerAdapter handlerAdapter) {
this.handlerAdapter = handlerAdapter;
}
/**
@@ -116,9 +116,10 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
if (logger.isDebugEnabled()) {
logger.debug("Processing [" + message + "]");
}
InvocationResult result = null;
try {
Object result = invokeHandler(amqpMessage, channel, message);
if (result != null) {
result = invokeHandler(amqpMessage, channel, message);
if (result.getReturnValue() != null) {
handleResult(result, amqpMessage, channel, message);
}
else {
@@ -128,9 +129,9 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
catch (ListenerExecutionFailedException e) {
if (this.errorHandler != null) {
try {
Object result = this.errorHandler.handleError(amqpMessage, message, e);
if (result != null) {
handleResult(result, amqpMessage, channel, message);
Object errorResult = this.errorHandler.handleError(amqpMessage, message, e);
if (errorResult != null) {
handleResult(new InvocationResult(errorResult, null, null), amqpMessage, channel, message);
}
else {
logger.trace("Error handler returned no result");
@@ -152,10 +153,11 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
throw exceptionToThrow;
}
try {
handleResult(new RemoteInvocationResult(throwableToReturn), amqpMessage, channel, message);
handleResult(new InvocationResult(new RemoteInvocationResult(throwableToReturn), null, null),
amqpMessage, channel, message);
}
catch (ReplyFailureException rfe) {
if (void.class.equals(this.handlerMethod.getReturnType(message.getPayload()))) {
if (void.class.equals(this.handlerAdapter.getReturnType(message.getPayload()))) {
throw exceptionToThrow;
}
else {
@@ -176,10 +178,10 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
* @param message the messaging message.
* @return the result of invoking the handler.
*/
private Object invokeHandler(org.springframework.amqp.core.Message amqpMessage, Channel channel,
private InvocationResult invokeHandler(org.springframework.amqp.core.Message amqpMessage, Channel channel,
Message<?> message) {
try {
return this.handlerMethod.invoke(message, amqpMessage, channel);
return this.handlerAdapter.invoke(message, amqpMessage, channel);
}
catch (MessagingException ex) {
throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
@@ -187,34 +189,36 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
}
catch (Exception ex) {
throw new ListenerExecutionFailedException("Listener method '" +
this.handlerMethod.getMethodAsString(message.getPayload()) + "' threw exception", ex, amqpMessage);
this.handlerAdapter.getMethodAsString(message.getPayload()) + "' threw exception", ex, amqpMessage);
}
}
private String createMessagingErrorMessage(String description, Object payload) {
return description + "\n"
+ "Endpoint handler details:\n"
+ "Method [" + this.handlerMethod.getMethodAsString(payload) + "]\n"
+ "Bean [" + this.handlerMethod.getBean() + "]";
+ "Method [" + this.handlerAdapter.getMethodAsString(payload) + "]\n"
+ "Bean [" + this.handlerAdapter.getBean() + "]";
}
/**
* Build a Rabbit message to be sent as response based on the given result object.
* @param channel the Rabbit Channel to operate on
* @param result the content of the message, as returned from the listener method
* @param genericType the generic type of the result.
* @return the Rabbit <code>Message</code> (never <code>null</code>)
* @throws Exception if thrown by Rabbit API methods
* @see #setMessageConverter
*/
@Override
protected org.springframework.amqp.core.Message buildMessage(Channel channel, Object result) throws Exception {
protected org.springframework.amqp.core.Message buildMessage(Channel channel, Object result, Type genericType)
throws Exception {
MessageConverter converter = getMessageConverter();
if (converter != null && !(result instanceof org.springframework.amqp.core.Message)) {
if (result instanceof org.springframework.messaging.Message) {
return this.messagingMessageConverter.toMessage(result, new MessageProperties());
}
else {
return converter.toMessage(result, new MessageProperties());
return converter.toMessage(result, new MessageProperties(), genericType);
}
}
else {

View File

@@ -41,6 +41,7 @@ import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -160,7 +161,7 @@ public class EnableRabbitIntegrationTests {
"test.converted.args2", "test.converted.message", "test.notconverted.message",
"test.notconverted.channel", "test.notconverted.messagechannel", "test.notconverted.messagingmessage",
"test.converted.foomessage", "test.notconverted.messagingmessagenotgeneric", "test.simple.direct",
"test.simple.direct2",
"test.simple.direct2", "test.generic.list", "test.generic.map",
"amqp656dlq", "test.simple.declare", "test.return.exceptions", "test.pojo.errors", "test.pojo.errors2");
@Autowired
@@ -669,6 +670,17 @@ public class EnableRabbitIntegrationTests {
assertSame(value, typeCache.get(Foo1.class));
}
@Test
public void testGenericReturnTypes() {
Object returned = this.jsonRabbitTemplate.convertSendAndReceive("", "test.generic.list", new JsonObject("baz"));
assertThat(returned, instanceOf(List.class));
assertThat(((List<?>) returned).get(0), instanceOf(JsonObject.class));
returned = this.jsonRabbitTemplate.convertSendAndReceive("", "test.generic.map", new JsonObject("baz"));
assertThat(returned, instanceOf(Map.class));
assertThat(((Map<?, ?>) returned).get("key"), instanceOf(JsonObject.class));
}
interface TxService {
@Transactional
@@ -955,6 +967,43 @@ public class EnableRabbitIntegrationTests {
throw new Exception("return this");
}
@RabbitListener(queues = "test.generic.list", containerFactory = "simpleJsonListenerContainerFactory")
public List<JsonObject> genericList(JsonObject in) {
return Collections.singletonList(in);
}
@RabbitListener(queues = "test.generic.map", containerFactory = "simpleJsonListenerContainerFactory")
public Map<String, JsonObject> genericMap(JsonObject in) {
return Collections.singletonMap("key", in);
}
}
public static class JsonObject {
private String bar;
public JsonObject() {
super();
}
public JsonObject(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
@Override
public String toString() {
return "JsonObject [bar=" + this.bar + "]";
}
}
public static class Foo1 {
@@ -1147,7 +1196,9 @@ public class EnableRabbitIntegrationTests {
factory.setConnectionFactory(rabbitConnectionFactory());
factory.setErrorHandler(errorHandler());
factory.setConsumerTagStrategy(consumerTagStrategy());
factory.setMessageConverter(new Jackson2JsonMessageConverter());
Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
messageConverter.getJavaTypeMapper().addTrustedPackages("*");
factory.setMessageConverter(messageConverter);
factory.setReceiveTimeout(10L);
return factory;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -77,7 +77,7 @@ public class MessagingMessageListenerAdapterTests {
Channel session = mock(Channel.class);
MessagingMessageListenerAdapter listener = getSimpleInstance("echo", Message.class);
org.springframework.amqp.core.Message replyMessage = listener.buildMessage(session, result);
org.springframework.amqp.core.Message replyMessage = listener.buildMessage(session, result, null);
assertNotNull("reply should never be null", replyMessage);
assertEquals("Response", new String(replyMessage.getBody()));
@@ -229,7 +229,7 @@ public class MessagingMessageListenerAdapterTests {
protected MessagingMessageListenerAdapter createInstance(Method m, boolean returnExceptions) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, m, returnExceptions, null);
adapter.setHandlerMethod(new HandlerAdapter(factory.createInvocableHandlerMethod(sample, m)));
adapter.setHandlerAdapter(new HandlerAdapter(factory.createInvocableHandlerMethod(sample, m)));
return adapter;
}
@@ -246,7 +246,7 @@ public class MessagingMessageListenerAdapterTests {
methods.add(this.factory.createInvocableHandlerMethod(sample, m1));
methods.add(this.factory.createInvocableHandlerMethod(sample, m2));
DelegatingInvocableHandler handler = new DelegatingInvocableHandler(methods, this.sample, null, null);
adapter.setHandlerMethod(new HandlerAdapter(handler));
adapter.setHandlerAdapter(new HandlerAdapter(handler));
return adapter;
}