diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java index 4a9cf987b4..29cfaa10f7 100644 --- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java +++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -18,6 +18,7 @@ package org.springframework.jms.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -70,11 +71,13 @@ import org.springframework.messaging.handler.annotation.MessageMapping; * @since 4.1 * @see EnableJms * @see JmsListenerAnnotationBeanPostProcessor + * @see JmsListeners */ @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @MessageMapping @Documented +@Repeatable(JmsListeners.class) public @interface JmsListener { /** diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java index ce245f609d..373f8443a4 100644 --- a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java +++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListenerAnnotationBeanPostProcessor.java @@ -199,8 +199,8 @@ public class JmsListenerAnnotationBeanPostProcessor ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - JmsListener jmsListener = AnnotationUtils.getAnnotation(method, JmsListener.class); - if (jmsListener != null) { + for (JmsListener jmsListener : + AnnotationUtils.getRepeatableAnnotations(method, JmsListener.class, JmsListeners.class)) { processJmsListener(jmsListener, method, bean); annotatedMethods.add(method); } diff --git a/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java new file mode 100644 index 0000000000..f3d5266e52 --- /dev/null +++ b/spring-jms/src/main/java/org/springframework/jms/annotation/JmsListeners.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2015 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.jms.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Container annotation that aggregates several {@link JmsListener} annotations. + * + *

Can be used natively, declaring several nested {@link JmsListener} annotations. + * Can also be used in conjunction with Java 8's support for repeatable annotations, + * where {@link JmsListener} can simply be declared several times on the same method, + * implicitly generating this container annotation. + * + * @author Stephane Nicoll + * @since 4.2 + * @see JmsListener + */ +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface JmsListeners { + + JmsListener[] value(); + +} diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java index 612f8693bd..b58bfb4dd6 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/AbstractJmsAnnotationDrivenTests.java @@ -73,6 +73,12 @@ public abstract class AbstractJmsAnnotationDrivenTests { @Test public abstract void jmsHandlerMethodFactoryConfiguration() throws JMSException; + @Test + public abstract void jmsListenerIsRepeatable(); + + @Test + public abstract void jmsListeners(); + /** * Test for {@link SampleBean} discovery. If a factory with the default name * is set, an endpoint will use it automatically @@ -234,6 +240,50 @@ public abstract class AbstractJmsAnnotationDrivenTests { } } + /** + * Test for {@link JmsListenerRepeatableBean} and {@link JmsListenersBean} that validates that the + * {@code @JmsListener} annotation is repeatable and generate one specific container per annotation. + */ + public void testJmsListenerRepeatable(ApplicationContext context) { + JmsListenerContainerTestFactory simpleFactory = + context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class); + assertEquals(2, simpleFactory.getListenerContainers().size()); + + MethodJmsListenerEndpoint first = (MethodJmsListenerEndpoint) + simpleFactory.getListenerContainer("first").getEndpoint(); + assertEquals("first", first.getId()); + assertEquals("myQueue", first.getDestination()); + assertEquals(null, first.getConcurrency()); + + MethodJmsListenerEndpoint second = (MethodJmsListenerEndpoint) + simpleFactory.getListenerContainer("second").getEndpoint(); + assertEquals("second", second.getId()); + assertEquals("anotherQueue", second.getDestination()); + assertEquals("2-10", second.getConcurrency()); + } + + @Component + static class JmsListenerRepeatableBean { + + @JmsListener(id = "first", destination = "myQueue") + @JmsListener(id = "second", destination = "anotherQueue", concurrency = "2-10") + public void repeatableHandle(String msg) { + } + + } + + @Component + static class JmsListenersBean { + + @JmsListeners({ + @JmsListener(id = "first", destination = "myQueue"), + @JmsListener(id = "second", destination = "anotherQueue", concurrency = "2-10") + }) + public void repeatableHandle(String msg) { + } + + } + static class TestValidator implements Validator { @Override diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/AnnotationDrivenNamespaceTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/AnnotationDrivenNamespaceTests.java index 22f8a334ea..74f7b7844b 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/AnnotationDrivenNamespaceTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/AnnotationDrivenNamespaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -91,6 +91,20 @@ public class AnnotationDrivenNamespaceTests extends AbstractJmsAnnotationDrivenT testJmsHandlerMethodFactoryConfiguration(context); } + @Override + public void jmsListenerIsRepeatable() { + ApplicationContext context = new ClassPathXmlApplicationContext( + "annotation-driven-jms-listener-repeatable.xml", getClass()); + testJmsListenerRepeatable(context); + } + + @Override + public void jmsListeners() { + ApplicationContext context = new ClassPathXmlApplicationContext( + "annotation-driven-jms-listeners.xml", getClass()); + testJmsListenerRepeatable(context); + } + static class CustomJmsListenerConfigurer implements JmsListenerConfigurer { private MessageListener messageListener; diff --git a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java index e4a2613d01..45d2877df8 100644 --- a/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java +++ b/spring-jms/src/test/java/org/springframework/jms/annotation/EnableJmsTests.java @@ -112,6 +112,20 @@ public class EnableJmsTests extends AbstractJmsAnnotationDrivenTests { testJmsHandlerMethodFactoryConfiguration(context); } + @Override + public void jmsListenerIsRepeatable() { + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + EnableJmsDefaultContainerFactoryConfig.class, JmsListenerRepeatableBean.class); + testJmsListenerRepeatable(context); + } + + @Override + public void jmsListeners() { + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + EnableJmsDefaultContainerFactoryConfig.class, JmsListenersBean.class); + testJmsListenerRepeatable(context); + } + @Test public void unknownFactory() { thrown.expect(BeanCreationException.class); diff --git a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerTestFactory.java b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerTestFactory.java index f67d976fcf..d51e06ee63 100644 --- a/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerTestFactory.java +++ b/spring-jms/src/test/java/org/springframework/jms/config/JmsListenerContainerTestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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,25 +17,30 @@ package org.springframework.jms.config; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** - * * @author Stephane Nicoll */ public class JmsListenerContainerTestFactory implements JmsListenerContainerFactory { - private final List listenerContainers = - new ArrayList(); + private final Map listenerContainers = + new LinkedHashMap<>(); public List getListenerContainers() { - return listenerContainers; + return new ArrayList<>(this.listenerContainers.values()); + } + + public MessageListenerTestContainer getListenerContainer(String id) { + return this.listenerContainers.get(id); } @Override public MessageListenerTestContainer createListenerContainer(JmsListenerEndpoint endpoint) { MessageListenerTestContainer container = new MessageListenerTestContainer(endpoint); - this.listenerContainers.add(container); + this.listenerContainers.put(endpoint.getId(), container); return container; } diff --git a/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listener-repeatable.xml b/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listener-repeatable.xml new file mode 100644 index 0000000000..5769015ed6 --- /dev/null +++ b/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listener-repeatable.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listeners.xml b/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listeners.xml new file mode 100644 index 0000000000..69580e2a5b --- /dev/null +++ b/spring-jms/src/test/resources/org/springframework/jms/annotation/annotation-driven-jms-listeners.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/asciidoc/integration.adoc b/src/asciidoc/integration.adoc index 89931ef3dd..f3a86a6d3f 100644 --- a/src/asciidoc/integration.adoc +++ b/src/asciidoc/integration.adoc @@ -2577,6 +2577,10 @@ provides). The annotated endpoint infrastructure creates a message listener container behind the scenes for each annotated method, using a `JmsListenerContainerFactory`. +TIP: `@JmsListener` is a _repeatable_ annotation so it is possible to associate several +JMS destinations to the same method by adding additional `@JmsListener` declaration on +it. For pre Java8 use cases, you can use `@JmsListeners`. + [[jms-annotated-support]] ==== Enable listener endpoint annotations