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:
committed by
Artem Bilan
parent
bf5498db80
commit
38f59a2537
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user