Skip to content

Commit da9c80c

Browse files
committed
Revised method selection for JMS listeners (and their parameters)
Issue: SPR-13576
1 parent d5efe4f commit da9c80c

File tree

4 files changed

+138
-80
lines changed

4 files changed

+138
-80
lines changed

spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.Collections;
21-
import java.util.LinkedHashSet;
2221
import java.util.Map;
2322
import java.util.Set;
2423
import java.util.concurrent.ConcurrentHashMap;
@@ -37,6 +36,7 @@
3736
import org.springframework.beans.factory.SmartInitializingSingleton;
3837
import org.springframework.beans.factory.config.BeanPostProcessor;
3938
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
39+
import org.springframework.core.MethodIntrospector;
4040
import org.springframework.core.Ordered;
4141
import org.springframework.core.annotation.AnnotationUtils;
4242
import org.springframework.jms.config.JmsListenerConfigUtils;
@@ -48,7 +48,6 @@
4848
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
4949
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
5050
import org.springframework.util.Assert;
51-
import org.springframework.util.ReflectionUtils;
5251
import org.springframework.util.StringUtils;
5352

5453
/**
@@ -194,26 +193,30 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
194193
@Override
195194
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
196195
if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
197-
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
198196
Class<?> targetClass = AopUtils.getTargetClass(bean);
199-
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
200-
@Override
201-
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
202-
for (JmsListener jmsListener :
203-
AnnotationUtils.getRepeatableAnnotations(method, JmsListener.class, JmsListeners.class)) {
204-
processJmsListener(jmsListener, method, bean);
205-
annotatedMethods.add(method);
206-
}
207-
}
208-
});
197+
Map<Method, Set<JmsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
198+
new MethodIntrospector.MetadataLookup<Set<JmsListener>>() {
199+
@Override
200+
public Set<JmsListener> inspect(Method method) {
201+
Set<JmsListener> listenerMethods =
202+
AnnotationUtils.getRepeatableAnnotations(method, JmsListener.class, JmsListeners.class);
203+
return (!listenerMethods.isEmpty() ? listenerMethods : null);
204+
}
205+
});
209206
if (annotatedMethods.isEmpty()) {
210207
this.nonAnnotatedClasses.add(bean.getClass());
211208
if (logger.isTraceEnabled()) {
212-
logger.trace("No @JmsListener annotations found on bean class: " + bean.getClass());
209+
logger.trace("No @JmsListener annotations found on bean type: " + bean.getClass());
213210
}
214211
}
215212
else {
216213
// Non-empty set of methods
214+
for (Map.Entry<Method, Set<JmsListener>> entry : annotatedMethods.entrySet()) {
215+
Method method = entry.getKey();
216+
for (JmsListener listener : entry.getValue()) {
217+
processJmsListener(listener, method, bean);
218+
}
219+
}
217220
if (logger.isDebugEnabled()) {
218221
logger.debug(annotatedMethods.size() + " @JmsListener methods processed on bean '" + beanName +
219222
"': " + annotatedMethods);
@@ -223,29 +226,13 @@ public void doWith(Method method) throws IllegalArgumentException, IllegalAccess
223226
return bean;
224227
}
225228

226-
protected void processJmsListener(JmsListener jmsListener, Method method, Object bean) {
227-
if (AopUtils.isJdkDynamicProxy(bean)) {
228-
try {
229-
// Found a @JmsListener method on the target class for this JDK proxy ->
230-
// is it also present on the proxy itself?
231-
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
232-
}
233-
catch (SecurityException ex) {
234-
ReflectionUtils.handleReflectionException(ex);
235-
}
236-
catch (NoSuchMethodException ex) {
237-
throw new IllegalStateException(String.format(
238-
"@JmsListener method '%s' found on bean target class '%s', " +
239-
"but not found in any interface(s) for bean JDK proxy. Either " +
240-
"pull the method up to an interface or switch to subclass (CGLIB) " +
241-
"proxies by setting proxy-target-class/proxyTargetClass " +
242-
"attribute to 'true'", method.getName(), method.getDeclaringClass().getSimpleName()));
243-
}
244-
}
229+
protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) {
230+
Method invocableMethod = MethodIntrospector.selectInvocableMethod(mostSpecificMethod, bean.getClass());
245231

246232
MethodJmsListenerEndpoint endpoint = new MethodJmsListenerEndpoint();
247233
endpoint.setBean(bean);
248-
endpoint.setMethod(method);
234+
endpoint.setMethod(invocableMethod);
235+
endpoint.setMostSpecificMethod(mostSpecificMethod);
249236
endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
250237
endpoint.setBeanFactory(this.beanFactory);
251238
endpoint.setId(getEndpointId(jmsListener));
@@ -268,9 +255,9 @@ protected void processJmsListener(JmsListener jmsListener, Method method, Object
268255
factory = this.beanFactory.getBean(containerFactoryBeanName, JmsListenerContainerFactory.class);
269256
}
270257
catch (NoSuchBeanDefinitionException ex) {
271-
throw new BeanInitializationException("Could not register jms listener endpoint on [" +
272-
method + "], no " + JmsListenerContainerFactory.class.getSimpleName() + " with id '" +
273-
containerFactoryBeanName + "' was found in the application context", ex);
258+
throw new BeanInitializationException("Could not register JMS listener endpoint on [" +
259+
mostSpecificMethod + "], no " + JmsListenerContainerFactory.class.getSimpleName() +
260+
" with id '" + containerFactoryBeanName + "' was found in the application context", ex);
274261
}
275262
}
276263

spring-jms/src/main/java/org/springframework/jms/config/MethodJmsListenerEndpoint.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* an incoming message for this endpoint.
4040
*
4141
* @author Stephane Nicoll
42+
* @author Juergen Hoeller
4243
* @since 4.1
4344
*/
4445
public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
@@ -47,6 +48,8 @@ public class MethodJmsListenerEndpoint extends AbstractJmsListenerEndpoint {
4748

4849
private Method method;
4950

51+
private Method mostSpecificMethod;
52+
5053
private MessageHandlerMethodFactory messageHandlerMethodFactory;
5154

5255
private BeanFactory beanFactory;
@@ -74,6 +77,29 @@ public Method getMethod() {
7477
return this.method;
7578
}
7679

80+
/**
81+
* Set the most specific method known for this endpoint's declaration.
82+
* <p>In case of a proxy, this will be the method on the target class
83+
* (if annotated itself, that is, if not just annotated in an interface).
84+
* @since 4.2.3
85+
*/
86+
public void setMostSpecificMethod(Method mostSpecificMethod) {
87+
this.mostSpecificMethod = mostSpecificMethod;
88+
}
89+
90+
public Method getMostSpecificMethod() {
91+
if (this.mostSpecificMethod != null) {
92+
return this.mostSpecificMethod;
93+
}
94+
else if (AopUtils.isAopProxy(this.bean)) {
95+
Class<?> target = AopProxyUtils.ultimateTargetClass(this.bean);
96+
return AopUtils.getMostSpecificMethod(getMethod(), target);
97+
}
98+
else {
99+
return getMethod();
100+
}
101+
}
102+
77103
/**
78104
* Set the {@link MessageHandlerMethodFactory} to use to build the
79105
* {@link InvocableHandlerMethod} responsible to manage the invocation
@@ -134,8 +160,8 @@ protected String getDefaultResponseDestination() {
134160
if (ann != null) {
135161
Object[] destinations = ann.value();
136162
if (destinations.length != 1) {
137-
throw new IllegalStateException("Invalid @" + SendTo.class.getSimpleName() + " annotation on '"
138-
+ specificMethod + "' one destination must be set (got " + Arrays.toString(destinations) + ")");
163+
throw new IllegalStateException("Invalid @" + SendTo.class.getSimpleName() + " annotation on '" +
164+
specificMethod + "' one destination must be set (got " + Arrays.toString(destinations) + ")");
139165
}
140166
return resolve((String) destinations[0]);
141167
}
@@ -154,16 +180,6 @@ private String resolve(String value) {
154180
}
155181

156182

157-
private Method getMostSpecificMethod() {
158-
if (AopUtils.isAopProxy(this.bean)) {
159-
Class<?> target = AopProxyUtils.ultimateTargetClass(this.bean);
160-
return AopUtils.getMostSpecificMethod(getMethod(), target);
161-
}
162-
else {
163-
return getMethod();
164-
}
165-
}
166-
167183
@Override
168184
protected StringBuilder getEndpointDescription() {
169185
return super.getEndpointDescription()

spring-jms/src/test/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessorTests.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.junit.Test;
2727
import org.junit.rules.ExpectedException;
2828

29+
import org.springframework.aop.support.AopUtils;
2930
import org.springframework.beans.factory.BeanCreationException;
3031
import org.springframework.context.ConfigurableApplicationContext;
3132
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -58,8 +59,9 @@ public class JmsListenerAnnotationBeanPostProcessorTests {
5859
@Rule
5960
public final ExpectedException thrown = ExpectedException.none();
6061

62+
6163
@Test
62-
public void simpleMessageListener() {
64+
public void simpleMessageListener() throws Exception {
6365
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
6466
Config.class, SimpleMessageListenerTestBean.class);
6567

@@ -70,8 +72,9 @@ public void simpleMessageListener() {
7072
JmsListenerEndpoint endpoint = container.getEndpoint();
7173
assertEquals("Wrong endpoint type", MethodJmsListenerEndpoint.class, endpoint.getClass());
7274
MethodJmsListenerEndpoint methodEndpoint = (MethodJmsListenerEndpoint) endpoint;
73-
assertNotNull(methodEndpoint.getBean());
74-
assertNotNull(methodEndpoint.getMethod());
75+
assertEquals(SimpleMessageListenerTestBean.class, methodEndpoint.getBean().getClass());
76+
assertEquals(SimpleMessageListenerTestBean.class.getMethod("handleIt", String.class), methodEndpoint.getMethod());
77+
assertEquals(SimpleMessageListenerTestBean.class.getMethod("handleIt", String.class), methodEndpoint.getMostSpecificMethod());
7578

7679
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
7780
methodEndpoint.setupListenerContainer(listenerContainer);
@@ -83,14 +86,20 @@ public void simpleMessageListener() {
8386
}
8487

8588
@Test
86-
public void metaAnnotationIsDiscovered() {
89+
public void metaAnnotationIsDiscovered() throws Exception {
8790
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
8891
Config.class, MetaAnnotationTestBean.class);
8992

9093
try {
9194
JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class);
9295
assertEquals("one container should have been registered", 1, factory.getListenerContainers().size());
96+
9397
JmsListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint();
98+
assertEquals("Wrong endpoint type", MethodJmsListenerEndpoint.class, endpoint.getClass());
99+
MethodJmsListenerEndpoint methodEndpoint = (MethodJmsListenerEndpoint) endpoint;
100+
assertEquals(MetaAnnotationTestBean.class, methodEndpoint.getBean().getClass());
101+
assertEquals(MetaAnnotationTestBean.class.getMethod("handleIt", String.class), methodEndpoint.getMethod());
102+
assertEquals(MetaAnnotationTestBean.class.getMethod("handleIt", String.class), methodEndpoint.getMostSpecificMethod());
94103
assertEquals("metaTestQueue", ((AbstractJmsListenerEndpoint) endpoint).getDestination());
95104
}
96105
finally {
@@ -99,13 +108,21 @@ public void metaAnnotationIsDiscovered() {
99108
}
100109

101110
@Test
102-
public void sendToAnnotationFoundOnProxy() {
111+
public void sendToAnnotationFoundOnProxy() throws Exception {
103112
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
104113
Config.class, ProxyConfig.class, ProxyTestBean.class);
105114
try {
106115
JmsListenerContainerTestFactory factory = context.getBean(JmsListenerContainerTestFactory.class);
107116
assertEquals("one container should have been registered", 1, factory.getListenerContainers().size());
117+
108118
JmsListenerEndpoint endpoint = factory.getListenerContainers().get(0).getEndpoint();
119+
assertEquals("Wrong endpoint type", MethodJmsListenerEndpoint.class, endpoint.getClass());
120+
MethodJmsListenerEndpoint methodEndpoint = (MethodJmsListenerEndpoint) endpoint;
121+
assertTrue(AopUtils.isJdkDynamicProxy(methodEndpoint.getBean()));
122+
assertTrue(methodEndpoint.getBean() instanceof SimpleService);
123+
assertEquals(SimpleService.class.getMethod("handleIt", String.class), methodEndpoint.getMethod());
124+
assertEquals(ProxyTestBean.class.getMethod("handleIt", String.class), methodEndpoint.getMostSpecificMethod());
125+
109126
Method m = ReflectionUtils.findMethod(endpoint.getClass(), "getDefaultResponseDestination");
110127
ReflectionUtils.makeAccessible(m);
111128
Object destination = ReflectionUtils.invokeMethod(m, endpoint);
@@ -132,7 +149,6 @@ static class SimpleMessageListenerTestBean {
132149
@JmsListener(destination = "testQueue")
133150
public void handleIt(String body) {
134151
}
135-
136152
}
137153

138154

@@ -174,6 +190,7 @@ public JmsListenerContainerTestFactory testFactory() {
174190
}
175191
}
176192

193+
177194
@Configuration
178195
@EnableTransactionManagement
179196
static class ProxyConfig {
@@ -182,15 +199,15 @@ static class ProxyConfig {
182199
public PlatformTransactionManager transactionManager() {
183200
return mock(PlatformTransactionManager.class);
184201
}
185-
186202
}
187203

204+
188205
interface SimpleService {
189206

190207
void handleIt(String body);
191-
192208
}
193209

210+
194211
@Component
195212
static class ProxyTestBean implements SimpleService {
196213

@@ -199,10 +216,10 @@ static class ProxyTestBean implements SimpleService {
199216
@JmsListener(destination = "testQueue")
200217
@SendTo("foobar")
201218
public void handleIt(String body) {
202-
203219
}
204220
}
205221

222+
206223
@Component
207224
static class InvalidProxyTestBean implements SimpleService {
208225

0 commit comments

Comments
 (0)