AMQP-710: Fix Exception Return From Void Method

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

Throw the original exception if `returnException` is set for a
method with void return, with no return address.

Polishing
This commit is contained in:
Gary Russell
2017-02-13 21:40:53 -05:00
committed by Artem Bilan
parent 3e2c48382d
commit e1d1dd29c0
4 changed files with 148 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2017 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.
@@ -225,4 +225,15 @@ public class DelegatingInvocableHandler {
return handlerForPayload == null ? "no match" : handlerForPayload.getMethod().toGenericString(); //NOSONAR
}
/**
* Return the method that will be invoked for this payload.
* @param payload the payload.
* @return the method.
* @since 2.0
*/
public Method getMethodFor(Object payload) {
InvocableHandlerMethod handlerForPayload = getHandlerForPayload(payload.getClass());
return handlerForPayload == null ? null : handlerForPayload.getMethod();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2017 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,6 +16,8 @@
package org.springframework.amqp.rabbit.listener.adapter;
import java.lang.reflect.Method;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
@@ -62,6 +64,22 @@ public class HandlerAdapter {
}
}
/**
* Return the return type for the method that will be chosen for this payload.
* @param payload the payload.
* @return the return type, or null if no handler found.
* @since 2.0
*/
public Object getReturnType(Object payload) {
if (this.invokerHandlerMethod != null) {
return this.invokerHandlerMethod.getMethod().getReturnType();
}
else {
Method method = this.delegatingHandler.getMethodFor(payload);
return method == null ? null : method.getReturnType();
}
}
public Object getBean() {
if (this.invokerHandlerMethod != null) {
return this.invokerHandlerMethod.getBean();

View File

@@ -137,17 +137,29 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
}
}
catch (Exception ex) {
if (!this.returnExceptions) {
throw ex;
}
handleResult(new RemoteInvocationResult(ex), amqpMessage, channel, message);
returnOrThrow(amqpMessage, channel, message, ex, ex);
}
}
else {
if (!this.returnExceptions) {
throw e;
}
handleResult(new RemoteInvocationResult(e.getCause()), amqpMessage, channel, message);
returnOrThrow(amqpMessage, channel, message, e.getCause(), e);
}
}
}
private void returnOrThrow(org.springframework.amqp.core.Message amqpMessage, Channel channel, Message<?> message,
Throwable throwableToReturn, Exception exceptionToThrow) throws Exception {
if (!this.returnExceptions) {
throw exceptionToThrow;
}
try {
handleResult(new RemoteInvocationResult(throwableToReturn), amqpMessage, channel, message);
}
catch (ReplyFailureException rfe) {
if (void.class.equals(this.handlerMethod.getReturnType(message.getPayload()))) {
throw exceptionToThrow;
}
else {
throw rfe;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 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,27 +16,38 @@
package org.springframework.amqp.rabbit.listener.adapter;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import org.springframework.amqp.rabbit.test.MessageTestUtils;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.beans.factory.support.StaticListableBeanFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.ReflectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
/**
@@ -90,10 +101,62 @@ public class MessagingMessageListenerAdapterTests {
assertEquals("Expected test exception", ex.getCause().getMessage());
}
catch (Exception ex) {
fail("Should not have thrown another exception");
fail("Should not have thrown an " + ex.getClass().getSimpleName());
}
}
@Test
public void exceptionInListenerBadReturnExceptionSetting() {
org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo");
Channel channel = mock(Channel.class);
MessagingMessageListenerAdapter listener = getSimpleInstance("fail", true, String.class);
try {
listener.onMessage(message, channel);
fail("Should have thrown an exception");
}
catch (ListenerExecutionFailedException ex) {
assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
assertEquals("Expected test exception", ex.getCause().getMessage());
}
catch (Exception ex) {
fail("Should not have thrown an " + ex.getClass().getSimpleName());
}
}
@Test
public void exceptionInMultiListenerReturnException() throws Exception {
org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo");
Channel channel = mock(Channel.class);
MessagingMessageListenerAdapter listener = getMultiInstance("fail", "failWithReturn", true, String.class,
Integer.class);
try {
listener.onMessage(message, channel);
fail("Should have thrown an exception");
}
catch (ListenerExecutionFailedException ex) {
assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
assertEquals("Expected test exception", ex.getCause().getMessage());
}
catch (Exception ex) {
ex.printStackTrace();
fail("Should not have thrown an " + ex.getClass().getSimpleName());
}
message = new SimpleMessageConverter().toMessage(42, new MessageProperties());
try {
listener.onMessage(message, channel);
fail("Should have thrown an exception");
}
catch (ReplyFailureException ex) {
assertThat(ex.getMessage(), containsString("Failed to send reply"));
}
catch (Exception ex) {
fail("Should not have thrown an " + ex.getClass().getSimpleName());
}
message.getMessageProperties().setReplyTo("foo/bar");
listener.onMessage(message, channel);
verify(channel).basicPublish(eq("foo"), eq("bar"), eq(false), any(BasicProperties.class), any(byte[].class));
}
@Test
public void exceptionInInvocation() {
org.springframework.amqp.core.Message message = MessageTestUtils.createTextMessage("foo");
@@ -109,7 +172,7 @@ public class MessagingMessageListenerAdapterTests {
ex.getCause().getClass());
}
catch (Exception ex) {
fail("Should not have thrown another exception");
fail("Should not have thrown an " + ex.getClass().getSimpleName());
}
}
@@ -155,15 +218,38 @@ public class MessagingMessageListenerAdapterTests {
protected MessagingMessageListenerAdapter getSimpleInstance(String methodName, Class<?>... parameterTypes) {
Method m = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes);
return createInstance(m);
return createInstance(m, false);
}
protected MessagingMessageListenerAdapter createInstance(Method m) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, m);
protected MessagingMessageListenerAdapter getSimpleInstance(String methodName, boolean returnExceptions,
Class<?>... parameterTypes) {
Method m = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes);
return createInstance(m, returnExceptions);
}
protected MessagingMessageListenerAdapter createInstance(Method m, boolean returnExceptions) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, m, returnExceptions, null);
adapter.setHandlerMethod(new HandlerAdapter(factory.createInvocableHandlerMethod(sample, m)));
return adapter;
}
protected MessagingMessageListenerAdapter getMultiInstance(String methodName1, String methodName2,
boolean returnExceptions, Class<?> m1ParameterType, Class<?> m2ParameterType) {
Method m1 = ReflectionUtils.findMethod(SampleBean.class, methodName1, m1ParameterType);
Method m2 = ReflectionUtils.findMethod(SampleBean.class, methodName2, m2ParameterType);
return createMultiInstance(m1, m2, returnExceptions);
}
protected MessagingMessageListenerAdapter createMultiInstance(Method m1, Method m2, boolean returnExceptions) {
MessagingMessageListenerAdapter adapter = new MessagingMessageListenerAdapter(null, null, returnExceptions, null);
List<InvocableHandlerMethod> methods = new ArrayList<>();
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));
return adapter;
}
private void initializeFactory(DefaultMessageHandlerMethodFactory factory) {
factory.setBeanFactory(new StaticListableBeanFactory());
factory.afterPropertiesSet();
@@ -214,6 +300,11 @@ public class MessagingMessageListenerAdapterTests {
this.payload = message.getPayload();
}
@SuppressWarnings("unused")
public String failWithReturn(Integer input) {
throw new IllegalArgumentException("Expected test exception");
}
}
private static class Foo {