Skip to content

Commit 713dd60

Browse files
committed
JMS annotation-driven endpoints.
This commit adds the support of JMS annotated endpoint. Can be activated both by @EnableJms or <jms:annotation-driven/> and detects methods of managed beans annotated with @JmsListener, either directly or through a meta-annotation. Containers are created and managed under the cover by a registry at application startup time. Container creation is delegated to a JmsListenerContainerFactory that is identified by the containerFactory attribute of the JmsListener annotation. Containers can be retrieved from the registry using a custom id that can be specified directly on the annotation. A "factory-id" attribute is available on the container element of the XML namespace. When it is present, the configuration defined at the namespace level is used to build a JmsListenerContainerFactory that is exposed with the value of the "factory-id" attribute. This can be used as a smooth migration path for users having listener containers defined at the namespace level. It is also possible to migrate all listeners to annotated endpoints and yet keep the <jms:listener-container> or <jms:jca-listener-container> element to share the container configuration. The configuration can be fine-tuned by implementing the JmsListenerConfigurer interface which gives access to the registrar used to register endpoints. This includes a programmatic registration of endpoints in complement to the declarative approach. A default JmsListenerContainerFactory can also be specified to be used if no containerFactory has been set on the annotation. Annotated methods can have flexible method arguments that are similar to what @MessageMapping provides. In particular, jms listener endpoint methods can fully use the messaging abstraction, including convenient header accessors. It is also possible to inject the raw javax.jms.Message and the Session for more advanced use cases. The payload can be injected as long as the conversion service is able to convert it from the original type of the JMS payload. By default, a DefaultJmsHandlerMethodFactory is used but it can be configured further to support additional method arguments or to customize conversion and validation support. The return type of an annotated method can also be an instance of Spring's Message abstraction. Instead of just converting the payload, such response type allows to communicate standard and custom headers. The JmsHeaderMapper infrastructure from Spring integration has also been migrated to the Spring framework. SimpleJmsHeaderMapper is based on SI's DefaultJmsHeaderMapper. The simple implementation maps all JMS headers so that the generated Message abstraction has all the information stored in the protocol specific message. Issue: SPR-9882
1 parent 6cb9a14 commit 713dd60

File tree

71 files changed

+7915
-457
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+7915
-457
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ project("spring-jms") {
485485
compile(project(":spring-beans"))
486486
compile(project(":spring-aop"))
487487
compile(project(":spring-context"))
488+
compile(project(":spring-messaging"))
488489
compile(project(":spring-tx"))
489490
provided("javax.jms:jms-api:1.1-rev-1")
490491
optional(project(":spring-oxm"))

spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,18 @@ public class AnnotationConfigUtils {
190190
public static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
191191
"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
192192

193+
/**
194+
* The bean name of the internally managed jms listener annotation processor.
195+
*/
196+
public static final String JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME =
197+
"org.springframework.jms.config.internalJmsListenerAnnotationProcessor";
198+
199+
/**
200+
* The bean name of the internally managed jms listener endpoint registry.
201+
*/
202+
public static final String JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME =
203+
"org.springframework.jms.config.internalJmsListenerEndpointRegistry";
204+
193205
/**
194206
* The bean name of the internally managed JPA annotation processor.
195207
*/
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright 2002-2014 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+
import org.springframework.context.annotation.Import;
26+
27+
/**
28+
* Enable JMS listener annotated endpoints that are created under the cover
29+
* by a {@link org.springframework.jms.config.JmsListenerContainerFactory
30+
* JmsListenerContainerFactory}. To be used on
31+
* @{@link org.springframework.context.annotation.Configuration Configuration} classes
32+
* as follows:
33+
*
34+
* <pre class="code">
35+
* &#064;Configuration
36+
* &#064;EnableJms
37+
* public class AppConfig {
38+
* &#064;Bean
39+
* public DefaultJmsListenerContainerFactory myJmsListenerContainerFactory() {
40+
* DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
41+
* factory.setConnectionFactory(connectionFactory());
42+
* factory.setDestinationResolver(destinationResolver());
43+
* factory.setConcurrency("5");
44+
* return factory;
45+
* }
46+
* // other &#064;Bean definitions
47+
* }</pre>
48+
*
49+
* The {@code JmsListenerContainerFactory} is responsible to create the listener container
50+
* responsible for a particular endpoint. Typical implementations, as the
51+
* {@link org.springframework.jms.config.DefaultJmsListenerContainerFactory DefaultJmsListenerContainerFactory}
52+
* used in the sample above, provides the necessary configuration options that are supported by
53+
* the underlying {@link org.springframework.jms.listener.MessageListenerContainer MessageListenerContainer}.
54+
*
55+
* <p>{@code @EnableJms} enables detection of @{@link JmsListener} annotations on
56+
* any Spring-managed bean in the container. For example, given a class {@code MyService}
57+
*
58+
* <pre class="code">
59+
* package com.acme.foo;
60+
*
61+
* public class MyService {
62+
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
63+
* public void process(String msg) {
64+
* // process incoming message
65+
* }
66+
* }</pre>
67+
*
68+
* The container factory to use is identified by the {@link JmsListener#containerFactory() containerFactory}
69+
* attribute defining the name of the {@code JmsListenerContainerFactory} bean to use.
70+
*
71+
* <p>the following configuration would ensure that every time a {@link javax.jms.Message}
72+
* is received on the {@link javax.jms.Destination} named "myQueue", {@code MyService.process()}
73+
* is called with the content of the message:
74+
*
75+
* <pre class="code">
76+
* &#064;Configuration
77+
* &#064;EnableJms
78+
* public class AppConfig {
79+
* &#064;Bean
80+
* public MyService myService() {
81+
* return new MyService();
82+
* }
83+
*
84+
* // JMS infrastructure setup
85+
* }</pre>
86+
*
87+
* Alternatively, if {@code MyService} were annotated with {@code @Component}, the
88+
* following configuration would ensure that its {@code @JmsListener} annotated
89+
* method is invoked with a matching incoming message:
90+
*
91+
* <pre class="code">
92+
* &#064;Configuration
93+
* &#064;EnableJms
94+
* &#064;ComponentScan(basePackages="com.acme.foo")
95+
* public class AppConfig {
96+
* }</pre>
97+
*
98+
* Note that the created containers are not registered against the application context
99+
* but can be easily located for management purposes using the
100+
* {@link org.springframework.jms.config.JmsListenerEndpointRegistry JmsListenerEndpointRegistry}.
101+
*
102+
* <p>Annotated methods can use flexible signature; in particular, it is possible to use
103+
* the {@link org.springframework.messaging.Message Message} abstraction and related annotations,
104+
* see @{@link JmsListener} Javadoc for more details. For instance, the following would
105+
* inject the content of the message and a a custom "myCounter" JMS header:
106+
*
107+
* <pre class="code">
108+
* &#064;JmsListener(containerFactory = "myJmsListenerContainerFactory", destination="myQueue")
109+
* public void process(String msg, @Header("myCounter") int counter) {
110+
* // process incoming message
111+
* }</pre>
112+
*
113+
* These features are abstracted by the {@link org.springframework.jms.config.JmsHandlerMethodFactory
114+
* JmsHandlerMethodFactory} that is responsible to build the necessary invoker to process
115+
* the annotated method. By default, {@link org.springframework.jms.config.DefaultJmsHandlerMethodFactory
116+
* DefaultJmsHandlerMethodFactory} is used.
117+
*
118+
* <p>When more control is desired, a {@code @Configuration} class may implement
119+
* {@link JmsListenerConfigurer}. This allows access to the underlying
120+
* {@link org.springframework.jms.config.JmsListenerEndpointRegistrar JmsListenerEndpointRegistrar}
121+
* instance. The following example demonstrates how to specify a default
122+
* {@code JmsListenerContainerFactory} so that {@link JmsListener#containerFactory()} may be
123+
* omitted for endpoints willing to use the <em>default</em> container factory.
124+
*
125+
* <pre class="code">
126+
* &#064;Configuration
127+
* &#064;EnableJms
128+
* public class AppConfig implements JmsListenerConfigurer {
129+
* &#064;Override
130+
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
131+
* registrar.setDefaultContainerFactory(myJmsListenerContainerFactory());
132+
* }
133+
*
134+
* &#064;Bean
135+
* public JmsListenerContainerFactory<?> myJmsListenerContainerFactory() {
136+
* // factory settings
137+
* }
138+
*
139+
* &#064;Bean
140+
* public MyService myService() {
141+
* return new MyService();
142+
* }
143+
* }</pre>
144+
*
145+
* For reference, the example above can be compared to the following Spring XML
146+
* configuration:
147+
* <pre class="code">
148+
* {@code <beans>
149+
* <jms:annotation-driven default-container-factory="myJmsListenerContainerFactory"/>
150+
*
151+
* <bean id="myJmsListenerContainerFactory"
152+
* class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
153+
* // factory settings
154+
* </bean>
155+
*
156+
* <bean id="myService" class="com.acme.foo.MyService"/>
157+
* </beans>
158+
* }</pre>
159+
*
160+
* It is also possible to specify a custom {@link org.springframework.jms.config.JmsListenerEndpointRegistry
161+
* JmsListenerEndpointRegistry} in case you need more control on the way the containers
162+
* are created and managed. The example below also demonstrates how to customize the
163+
* {@code JmsHandlerMethodFactory} to use with a custom {@link org.springframework.validation.Validator
164+
* Validator} so that payloads annotated with {@link org.springframework.validation.annotation.Validated
165+
* @Validated} are first validated against a custom {@code Validator}.
166+
*
167+
* <pre class="code">
168+
* &#064;Configuration
169+
* &#064;EnableJms
170+
* public class AppConfig implements JmsListenerConfigurer {
171+
* &#064;Override
172+
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
173+
* registrar.setEndpointRegistry(myJmsListenerEndpointRegistry());
174+
* registrar.setJmsHandlerMethodFactory(myJmsHandlerMethodFactory);
175+
* }
176+
*
177+
* &#064;Bean
178+
* public JmsListenerEndpointRegistry<?> myJmsListenerEndpointRegistry() {
179+
* // registry configuration
180+
* }
181+
*
182+
* &#064;Bean
183+
* public JmsHandlerMethodFactory myJmsHandlerMethodFactory() {
184+
* DefaultJmsHandlerMethodFactory factory = new DefaultJmsHandlerMethodFactory();
185+
* factory.setValidator(new MyValidator());
186+
* return factory;
187+
* }
188+
*
189+
* &#064;Bean
190+
* public MyService myService() {
191+
* return new MyService();
192+
* }
193+
* }</pre>
194+
*
195+
* For reference, the example above can be compared to the following Spring XML
196+
* configuration:
197+
* <pre class="code">
198+
* {@code <beans>
199+
* <jms:annotation-driven registry="myJmsListenerEndpointRegistry"
200+
* handler-method-factory="myJmsHandlerMethodFactory"/&gt;
201+
*
202+
* <bean id="myJmsListenerEndpointRegistry"
203+
* class="org.springframework.jms.config.JmsListenerEndpointRegistry">
204+
* // registry configuration
205+
* </bean>
206+
*
207+
* <bean id="myJmsHandlerMethodFactory"
208+
* class="org.springframework.jms.config.DefaultJmsHandlerMethodFactory">
209+
* <property name="validator" ref="myValidator"/>
210+
* </bean>
211+
*
212+
* <bean id="myService" class="com.acme.foo.MyService"/>
213+
* </beans>
214+
* }</pre>
215+
*
216+
* Implementing {@code JmsListenerConfigurer} also allows for fine-grained
217+
* control over endpoints registration via the {@code JmsListenerEndpointRegistrar}.
218+
* For example, the following configures an extra endpoint:
219+
*
220+
* <pre class="code">
221+
* &#064;Configuration
222+
* &#064;EnableJms
223+
* public class AppConfig implements JmsListenerConfigurer {
224+
* &#064;Override
225+
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
226+
* SimpleJmsListenerEndpoint myEndpoint = new SimpleJmsListenerEndpoint();
227+
* // ... configure the endpoint
228+
* registrar.registerEndpoint(endpoint, anotherJmsListenerContainerFactory());
229+
* }
230+
*
231+
* &#064;Bean
232+
* public MyService myService() {
233+
* return new MyService();
234+
* }
235+
*
236+
* &#064;Bean
237+
* public JmsListenerContainerFactory<?> anotherJmsListenerContainerFactory() {
238+
* // ...
239+
* }
240+
*
241+
* // JMS infrastructure setup
242+
* }</pre>
243+
*
244+
* Note that all beans implementing {@code JmsListenerConfigurer} will be detected and
245+
* invoked in a similar fashion. The example above can be translated in a regular bean
246+
* definition registered in the context in case you use the XML configuration.
247+
*
248+
* @author Stephane Nicoll
249+
* @since 4.1
250+
* @see JmsListener
251+
* @see JmsListenerAnnotationBeanPostProcessor
252+
* @see org.springframework.jms.config.JmsListenerEndpointRegistrar
253+
* @see org.springframework.jms.config.JmsListenerEndpointRegistry
254+
*/
255+
@Target(ElementType.TYPE)
256+
@Retention(RetentionPolicy.RUNTIME)
257+
@Documented
258+
@Import(JmsBootstrapConfiguration.class)
259+
public @interface EnableJms {
260+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2002-2014 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 org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.context.annotation.AnnotationConfigUtils;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Role;
24+
import org.springframework.jms.config.JmsListenerEndpointRegistry;
25+
26+
/**
27+
* {@code @Configuration} class that registers a {@link JmsListenerAnnotationBeanPostProcessor}
28+
* bean capable of processing Spring's @{@link JmsListener} annotation. Also register
29+
* a default {@link JmsListenerEndpointRegistry}.
30+
*
31+
* <p>This configuration class is automatically imported when using the @{@link EnableJms}
32+
* annotation. See {@link EnableJms} Javadoc for complete usage.
33+
*
34+
* @author Stephane Nicoll
35+
* @since 4.1
36+
* @see JmsListenerAnnotationBeanPostProcessor
37+
* @see JmsListenerEndpointRegistry
38+
* @see EnableJms
39+
*/
40+
@Configuration
41+
public class JmsBootstrapConfiguration {
42+
43+
@Bean(name = AnnotationConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
44+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
45+
public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
46+
return new JmsListenerAnnotationBeanPostProcessor();
47+
}
48+
49+
@Bean(name = AnnotationConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
50+
public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
51+
return new JmsListenerEndpointRegistry();
52+
}
53+
54+
}

0 commit comments

Comments
 (0)