From adc7ad7fb20660c81cc36038e6e057a0b55c5a20 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 8 Dec 2014 10:56:02 +0100 Subject: [PATCH] Fix detection of the @SendTo annotation Previously, the default reply destination could not be discovered if the @JmsListener annotation was placed on a bean that is eligible for proxying as the proxy method is used internally and does not reveal an annotation placed on the implementation. This commit makes sure to resolve the most specific method when searching that annotation. Issue: SPR-12513 --- .../jms/config/MethodJmsListenerEndpoint.java | 17 +++- ...tenerAnnotationBeanPostProcessorTests.java | 99 ++++++++++++++++++- 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java index f22389fa15..29c9dec455 100644 --- a/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java +++ b/spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java @@ -19,6 +19,8 @@ package org.springframework.jms.config; import java.lang.reflect.Method; import java.util.Arrays; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.support.AopUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jms.listener.MessageListenerContainer; import org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter; @@ -109,18 +111,29 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint { } private String getDefaultResponseDestination() { - SendTo ann = AnnotationUtils.getAnnotation(getMethod(), SendTo.class); + Method specificMethod = getMostSpecificMethod(); + SendTo ann = AnnotationUtils.getAnnotation(specificMethod, SendTo.class); if (ann != null) { Object[] destinations = ann.value(); if (destinations.length != 1) { throw new IllegalStateException("Invalid @" + SendTo.class.getSimpleName() + " annotation on '" - + getMethod() + "' one destination must be set (got " + Arrays.toString(destinations) + ")"); + + specificMethod + "' one destination must be set (got " + Arrays.toString(destinations) + ")"); } return (String) destinations[0]; } return null; } + private Method getMostSpecificMethod() { + if (AopUtils.isAopProxy(this.bean)) { + Class target = AopProxyUtils.ultimateTargetClass(this.bean); + return AopUtils.getMostSpecificMethod(getMethod(), target); + } + else { + return getMethod(); + } + } + @Override protected StringBuilder getEndpointDescription() { return super.getEndpointDescription() diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java index fe96e08b82..6c313b4f0d 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java @@ -20,9 +20,13 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Method; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -34,9 +38,16 @@ import org.springframework.jms.config.JmsListenerEndpointRegistry; import org.springframework.jms.config.MessageListenerTestContainer; import org.springframework.jms.config.MethodJmsListenerEndpoint; import org.springframework.jms.listener.SimpleMessageListenerContainer; +import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ReflectionUtils; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * @author Stephane Nicoll @@ -44,6 +55,9 @@ import static org.junit.Assert.*; */ public class JmsListenerAnnotationBeanPostProcessorTests { + @Rule + public final ExpectedException thrown = ExpectedException.none(); + @Test public void simpleMessageListener() { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( @@ -73,10 +87,42 @@ public class JmsListenerAnnotationBeanPostProcessorTests { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( Config.class, MetaAnnotationTestBean.class); - JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class); - assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); - JmsListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); - assertEquals("metaTestQueue", ((AbstractJmsListenerEndpoint) endpoint).getDestination()); + try { + JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class); + assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); + JmsListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); + assertEquals("metaTestQueue", ((AbstractJmsListenerEndpoint) endpoint).getDestination()); + } + finally { + context.close(); + } + } + + @Test + public void sendToAnnotationFoundOnProxy() { + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + Config.class, ProxyConfig.class, ProxyTestBean.class); + try { + JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class); + assertEquals("one container should have been registered", 1, factory.getListenerContainers().size()); + JmsListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint(); + Method m = ReflectionUtils.findMethod(endpoint.getClass(), "getDefaultResponseDestination"); + ReflectionUtils.makeAccessible(m); + Object destination = ReflectionUtils.invokeMethod(m, endpoint); + assertEquals("SendTo annotation not found on proxy", "foobar", destination); + } + finally { + context.close(); + } + } + + @Test + public void invalidProxy() { + thrown.expect(BeanCreationException.class); + thrown.expectCause(is(instanceOf(IllegalStateException.class))); + thrown.expectMessage("handleIt2"); + new AnnotationConfigApplicationContext( + Config.class, ProxyConfig.class, InvalidProxyTestBean.class); } @@ -102,7 +148,7 @@ public class JmsListenerAnnotationBeanPostProcessorTests { @JmsListener(destination = "metaTestQueue") @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) - static @interface FooListener { + @interface FooListener { } @@ -128,4 +174,47 @@ public class JmsListenerAnnotationBeanPostProcessorTests { } } + @Configuration + @EnableTransactionManagement + static class ProxyConfig { + + @Bean + public PlatformTransactionManager transactionManager() { + return mock(PlatformTransactionManager.class); + } + + } + + interface SimpleService { + + void handleIt(String body); + + } + + @Component + static class ProxyTestBean implements SimpleService { + + @Override + @Transactional + @JmsListener(destination = "testQueue") + @SendTo("foobar") + public void handleIt(String body) { + + } + } + + @Component + static class InvalidProxyTestBean implements SimpleService { + + @Override + public void handleIt(String body) { + } + + @Transactional + @JmsListener(destination = "testQueue") + @SendTo("foobar") + public void handleIt2(String body) { + } + } + }