Skip to content

Commit 4631add

Browse files
committed
Add support for repeatable JmsListener
Previously, a method could only declare one Jms endpoint so if several destinations share the exact same business logic, you'd still need one separate method declaration per destination. We now make sure that JmsListener is a repeatable annotation, introducing JmsListeners for pre Java8 use cases. Issue: SPR-13147
1 parent 9ada55d commit 4631add

File tree

10 files changed

+180
-10
lines changed

10 files changed

+180
-10
lines changed

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

Lines changed: 4 additions & 1 deletion
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.
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Documented;
2020
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Repeatable;
2122
import java.lang.annotation.Retention;
2223
import java.lang.annotation.RetentionPolicy;
2324
import java.lang.annotation.Target;
@@ -70,11 +71,13 @@
7071
* @since 4.1
7172
* @see EnableJms
7273
* @see JmsListenerAnnotationBeanPostProcessor
74+
* @see JmsListeners
7375
*/
7476
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
7577
@Retention(RetentionPolicy.RUNTIME)
7678
@MessageMapping
7779
@Documented
80+
@Repeatable(JmsListeners.class)
7881
public @interface JmsListener {
7982

8083
/**

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ public Object postProcessAfterInitialization(final Object bean, String beanName)
199199
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
200200
@Override
201201
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
202-
JmsListener jmsListener = AnnotationUtils.getAnnotation(method, JmsListener.class);
203-
if (jmsListener != null) {
202+
for (JmsListener jmsListener :
203+
AnnotationUtils.getRepeatableAnnotations(method, JmsListener.class, JmsListeners.class)) {
204204
processJmsListener(jmsListener, method, bean);
205205
annotatedMethods.add(method);
206206
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jms.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Container annotation that aggregates several {@link JmsListener} annotations.
27+
*
28+
* <p>Can be used natively, declaring several nested {@link JmsListener} annotations.
29+
* Can also be used in conjunction with Java 8's support for repeatable annotations,
30+
* where {@link JmsListener} can simply be declared several times on the same method,
31+
* implicitly generating this container annotation.
32+
*
33+
* @author Stephane Nicoll
34+
* @since 4.2
35+
* @see JmsListener
36+
*/
37+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Documented
40+
public @interface JmsListeners {
41+
42+
JmsListener[] value();
43+
44+
}

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ public abstract class AbstractJmsAnnotationDrivenTests {
7373
@Test
7474
public abstract void jmsHandlerMethodFactoryConfiguration() throws JMSException;
7575

76+
@Test
77+
public abstract void jmsListenerIsRepeatable();
78+
79+
@Test
80+
public abstract void jmsListeners();
81+
7682
/**
7783
* Test for {@link SampleBean} discovery. If a factory with the default name
7884
* is set, an endpoint will use it automatically
@@ -234,6 +240,50 @@ public void defaultHandle(@Validated String msg) {
234240
}
235241
}
236242

243+
/**
244+
* Test for {@link JmsListenerRepeatableBean} and {@link JmsListenersBean} that validates that the
245+
* {@code @JmsListener} annotation is repeatable and generate one specific container per annotation.
246+
*/
247+
public void testJmsListenerRepeatable(ApplicationContext context) {
248+
JmsListenerContainerTestFactory simpleFactory =
249+
context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class);
250+
assertEquals(2, simpleFactory.getListenerContainers().size());
251+
252+
MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint)
253+
simpleFactory.getListenerContainer("first").getEndpoint();
254+
assertEquals("first", first.getId());
255+
assertEquals("myQueue", first.getDestination());
256+
assertEquals(null, first.getConcurrency());
257+
258+
MethodJmsListenerEndpoint second = (MethodJmsListenerEndpoint)
259+
simpleFactory.getListenerContainer("second").getEndpoint();
260+
assertEquals("second", second.getId());
261+
assertEquals("anotherQueue", second.getDestination());
262+
assertEquals("2-10", second.getConcurrency());
263+
}
264+
265+
@Component
266+
static class JmsListenerRepeatableBean {
267+
268+
@JmsListener(id = "first", destination = "myQueue")
269+
@JmsListener(id = "second", destination = "anotherQueue", concurrency = "2-10")
270+
public void repeatableHandle(String msg) {
271+
}
272+
273+
}
274+
275+
@Component
276+
static class JmsListenersBean {
277+
278+
@JmsListeners({
279+
@JmsListener(id = "first", destination = "myQueue"),
280+
@JmsListener(id = "second", destination = "anotherQueue", concurrency = "2-10")
281+
})
282+
public void repeatableHandle(String msg) {
283+
}
284+
285+
}
286+
237287
static class TestValidator implements Validator {
238288

239289
@Override

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

Lines changed: 15 additions & 1 deletion
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.
@@ -91,6 +91,20 @@ public void jmsHandlerMethodFactoryConfiguration() throws JMSException {
9191
testJmsHandlerMethodFactoryConfiguration(context);
9292
}
9393

94+
@Override
95+
public void jmsListenerIsRepeatable() {
96+
ApplicationContext context = new ClassPathXmlApplicationContext(
97+
"annotation-driven-jms-listener-repeatable.xml", getClass());
98+
testJmsListenerRepeatable(context);
99+
}
100+
101+
@Override
102+
public void jmsListeners() {
103+
ApplicationContext context = new ClassPathXmlApplicationContext(
104+
"annotation-driven-jms-listeners.xml", getClass());
105+
testJmsListenerRepeatable(context);
106+
}
107+
94108
static class CustomJmsListenerConfigurer implements JmsListenerConfigurer {
95109

96110
private MessageListener messageListener;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ public void jmsHandlerMethodFactoryConfiguration() throws JMSException {
112112
testJmsHandlerMethodFactoryConfiguration(context);
113113
}
114114

115+
@Override
116+
public void jmsListenerIsRepeatable() {
117+
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
118+
EnableJmsDefaultContainerFactoryConfig.class, JmsListenerRepeatableBean.class);
119+
testJmsListenerRepeatable(context);
120+
}
121+
122+
@Override
123+
public void jmsListeners() {
124+
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(
125+
EnableJmsDefaultContainerFactoryConfig.class, JmsListenersBean.class);
126+
testJmsListenerRepeatable(context);
127+
}
128+
115129
@Test
116130
public void unknownFactory() {
117131
thrown.expect(BeanCreationException.class);

spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerTestFactory.java

Lines changed: 11 additions & 6 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.
@@ -17,25 +17,30 @@
1717
package org.springframework.jms.config;
1818

1919
import java.util.ArrayList;
20+
import java.util.LinkedHashMap;
2021
import java.util.List;
22+
import java.util.Map;
2123

2224
/**
23-
*
2425
* @author Stephane Nicoll
2526
*/
2627
public class JmsListenerContainerTestFactory implements JmsListenerContainerFactory<MessageListenerTestContainer> {
2728

28-
private final List<MessageListenerTestContainer> listenerContainers =
29-
new ArrayList<MessageListenerTestContainer>();
29+
private final Map<String, MessageListenerTestContainer> listenerContainers =
30+
new LinkedHashMap<>();
3031

3132
public List<MessageListenerTestContainer> getListenerContainers() {
32-
return listenerContainers;
33+
return new ArrayList<>(this.listenerContainers.values());
34+
}
35+
36+
public MessageListenerTestContainer getListenerContainer(String id) {
37+
return this.listenerContainers.get(id);
3338
}
3439

3540
@Override
3641
public MessageListenerTestContainer createListenerContainer(JmsListenerEndpoint endpoint) {
3742
MessageListenerTestContainer container = new MessageListenerTestContainer(endpoint);
38-
this.listenerContainers.add(container);
43+
this.listenerContainers.put(endpoint.getId(), container);
3944
return container;
4045
}
4146

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:jms="http://www.springframework.org/schema/jms"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans
6+
http://www.springframework.org/schema/beans/spring-beans.xsd
7+
http://www.springframework.org/schema/jms
8+
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
9+
10+
<jms:annotation-driven/>
11+
12+
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$JmsListenerRepeatableBean"/>
13+
14+
<bean id="jmsListenerContainerFactory"
15+
class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
16+
17+
18+
</beans>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:jms="http://www.springframework.org/schema/jms"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans
6+
http://www.springframework.org/schema/beans/spring-beans.xsd
7+
http://www.springframework.org/schema/jms
8+
http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
9+
10+
<jms:annotation-driven/>
11+
12+
<bean class="org.springframework.jms.annotation.AbstractJmsAnnotationDrivenTests$JmsListenersBean"/>
13+
14+
<bean id="jmsListenerContainerFactory"
15+
class="org.springframework.jms.config.JmsListenerContainerTestFactory"/>
16+
17+
18+
</beans>

src/asciidoc/integration.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,6 +2577,10 @@ provides).
25772577
The annotated endpoint infrastructure creates a message listener container
25782578
behind the scenes for each annotated method, using a `JmsListenerContainerFactory`.
25792579

2580+
TIP: `@JmsListener` is a _repeatable_ annotation so it is possible to associate several
2581+
JMS destinations to the same method by adding additional `@JmsListener` declaration on
2582+
it. For pre Java8 use cases, you can use `@JmsListeners`.
2583+
25802584
[[jms-annotated-support]]
25812585
==== Enable listener endpoint annotations
25822586

0 commit comments

Comments
 (0)