Skip to content

Commit 4b0aba6

Browse files
committed
Default JmsListenerContainerFactory lookup
Prior to this commit, the default JmsListenerContainerFactory to use must be explicitly set. Since having a single container factory is a fairly common use case, we look up the default one automatically using the bean name "jmsListenerContainerFactory". It is still possible to provide an explicit default but since it refers more to "the" container factory to use, the parameter has been renamed to "containerFactory" which is shorter and more explicit. The lookup strategy is lazy: if all endpoints are providing an explicit container factory and no container factory with the "jmsListenerContainerFactory" bean name exists, no exception will be thrown. Issue : SPR-11706
1 parent 08f0395 commit 4b0aba6

15 files changed

+195
-81
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
* }</pre>
6767
*
6868
* 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.
69+
* attribute defining the name of the {@code JmsListenerContainerFactory} bean to use. When none
70+
* is set a {@code JmsListenerContainerFactory} bean with name {@code jmsListenerContainerFactory} is
71+
* assumed to be present.
7072
*
7173
* <p>the following configuration would ensure that every time a {@link javax.jms.Message}
7274
* is received on the {@link javax.jms.Destination} named "myQueue", {@code MyService.process()}
@@ -118,17 +120,16 @@
118120
* <p>When more control is desired, a {@code @Configuration} class may implement
119121
* {@link JmsListenerConfigurer}. This allows access to the underlying
120122
* {@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.
123+
* instance. The following example demonstrates how to specify an explicit default
124+
* {@code JmsListenerContainerFactory}
124125
*
125126
* <pre class="code">
126127
* &#064;Configuration
127128
* &#064;EnableJms
128129
* public class AppConfig implements JmsListenerConfigurer {
129130
* &#064;Override
130131
* public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
131-
* registrar.setDefaultContainerFactory(myJmsListenerContainerFactory());
132+
* registrar.setContainerFactory(myJmsListenerContainerFactory());
132133
* }
133134
*
134135
* &#064;Bean
@@ -146,7 +147,7 @@
146147
* configuration:
147148
* <pre class="code">
148149
* {@code <beans>
149-
* <jms:annotation-driven default-container-factory="myJmsListenerContainerFactory"/>
150+
* <jms:annotation-driven container-factory="myJmsListenerContainerFactory"/>
150151
*
151152
* <bean id="myJmsListenerContainerFactory"
152153
* class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
* Annotation that marks a method to be the target of a JMS message
2929
* listener on the specified {@link #destination()}. The {@link #containerFactory()}
3030
* identifies the {@link org.springframework.jms.config.JmsListenerContainerFactory
31-
* JmsListenerContainerFactory} to use to build the jms listener container. It may
32-
* be omitted as long as a <em>default</em> container factory has been defined.
31+
* JmsListenerContainerFactory} to use to build the jms listener container. If not
32+
* set, a <em>default</em> container factory is assumed to be available with a bean
33+
* name of {@code jmsListenerContainerFactory} unless an explicit default has been
34+
* provided through configuration.
3335
*
3436
* <p>Processing of {@code @JmsListener} annotations is performed by
3537
* registering a {@link JmsListenerAnnotationBeanPostProcessor}. This can be

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,18 @@
7373
public class JmsListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
7474
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
7575

76+
/**
77+
* The bean name of the default {@link JmsListenerContainerFactory}
78+
*/
79+
static final String DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "jmsListenerContainerFactory";
80+
7681
private final AtomicInteger counter = new AtomicInteger();
7782

7883
private ApplicationContext applicationContext;
7984

8085
private JmsListenerEndpointRegistry endpointRegistry;
8186

82-
private JmsListenerContainerFactory<?> defaultContainerFactory;
87+
private String containerFactoryBeanName = DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME;
8388

8489
private final JmsHandlerMethodFactoryAdapter jmsHandlerMethodFactory = new JmsHandlerMethodFactoryAdapter();
8590

@@ -99,12 +104,12 @@ public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
99104
}
100105

101106
/**
102-
* Set the default {@link JmsListenerContainerFactory} to use in case a
103-
* {@link JmsListener} does not define any.
104-
* {@linkplain JmsListener#containerFactory() containerFactory}
107+
* Set the name of the {@link JmsListenerContainerFactory} to use by default.
108+
* <p/>If none is specified, {@value #DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME}
109+
* is assumed to be defined.
105110
*/
106-
public void setDefaultContainerFactory(JmsListenerContainerFactory<?> defaultContainerFactory) {
107-
this.defaultContainerFactory = defaultContainerFactory;
111+
public void setContainerFactoryBeanName(String containerFactoryBeanName) {
112+
this.containerFactoryBeanName = containerFactoryBeanName;
108113
}
109114

110115
/**
@@ -209,6 +214,9 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
209214
for (JmsListenerConfigurer configurer : instances.values()) {
210215
configurer.configureJmsListeners(registrar);
211216
}
217+
218+
registrar.setApplicationContext(this.applicationContext);
219+
212220
if (registrar.getEndpointRegistry() == null) {
213221
if (endpointRegistry == null) {
214222
endpointRegistry = applicationContext
@@ -217,10 +225,13 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
217225
}
218226
registrar.setEndpointRegistry(endpointRegistry);
219227
}
220-
if (registrar.getDefaultContainerFactory() == null && defaultContainerFactory != null) {
221-
registrar.setDefaultContainerFactory(defaultContainerFactory);
228+
229+
if (this.containerFactoryBeanName != null) {
230+
registrar.setContainerFactoryBeanName(this.containerFactoryBeanName);
222231
}
223232

233+
234+
// Set the custom handler method factory once resolved by the configurer
224235
JmsHandlerMethodFactory handlerMethodFactory = registrar.getJmsHandlerMethodFactory();
225236
if (handlerMethodFactory != null) {
226237
this.jmsHandlerMethodFactory.setJmsHandlerMethodFactory(handlerMethodFactory);
@@ -235,7 +246,6 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
235246
}
236247
}
237248

238-
239249
private String getEndpointId(JmsListener jmsListener) {
240250
if (StringUtils.hasText(jmsListener.id())) {
241251
return jmsListener.id();
@@ -267,7 +277,7 @@ public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method m
267277

268278
private JmsHandlerMethodFactory getJmsHandlerMethodFactory() {
269279
if (jmsHandlerMethodFactory == null) {
270-
jmsHandlerMethodFactory= createDefaultJmsHandlerMethodFactory();
280+
jmsHandlerMethodFactory = createDefaultJmsHandlerMethodFactory();
271281
}
272282
return jmsHandlerMethodFactory;
273283
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
6464
else {
6565
registerDefaultEndpointRegistry(source, parserContext);
6666
}
67-
String defaultContainerFactory = element.getAttribute("default-container-factory");
68-
if (StringUtils.hasText(defaultContainerFactory)) {
69-
builder.addPropertyReference("defaultContainerFactory", defaultContainerFactory);
67+
68+
String containerFactory = element.getAttribute("container-factory");
69+
if (StringUtils.hasText(containerFactory)) {
70+
builder.addPropertyValue("containerFactoryBeanName", containerFactory);
7071
}
72+
7173
String handlerMethodFactory = element.getAttribute("handler-method-factory");
7274
if (StringUtils.hasText(handlerMethodFactory)) {
7375
builder.addPropertyReference("jmsHandlerMethodFactory", handlerMethodFactory);

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.List;
2121

2222
import org.springframework.beans.factory.InitializingBean;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.context.ApplicationContextAware;
2325
import org.springframework.util.Assert;
2426

2527
/**
@@ -30,14 +32,18 @@
3032
* @since 4.1
3133
* @see org.springframework.jms.annotation.JmsListenerConfigurer
3234
*/
33-
public class JmsListenerEndpointRegistrar implements InitializingBean {
35+
public class JmsListenerEndpointRegistrar implements ApplicationContextAware, InitializingBean {
3436

3537
private JmsListenerEndpointRegistry endpointRegistry;
3638

37-
private JmsListenerContainerFactory<?> defaultContainerFactory;
39+
private String containerFactoryBeanName;
40+
41+
private JmsListenerContainerFactory<?> containerFactory;
3842

3943
private JmsHandlerMethodFactory jmsHandlerMethodFactory;
4044

45+
private ApplicationContext applicationContext;
46+
4147
private final List<JmsListenerEndpointDescriptor> endpointDescriptors
4248
= new ArrayList<JmsListenerEndpointDescriptor>();
4349

@@ -57,20 +63,24 @@ public JmsListenerEndpointRegistry getEndpointRegistry() {
5763
}
5864

5965
/**
60-
* Set the default {@link JmsListenerContainerFactory} to use in case a
61-
* {@link JmsListenerEndpoint} is registered with a {@code null} container
62-
* factory.
66+
* Set the bean name of the {@link JmsListenerContainerFactory} to use in
67+
* case a {@link JmsListenerEndpoint} is registered with a {@code null}
68+
* container factory. Alternatively, the container factory instance can
69+
* be registered directly, see {@link #setContainerFactory(JmsListenerContainerFactory)}
6370
*/
64-
public void setDefaultContainerFactory(JmsListenerContainerFactory<?> defaultContainerFactory) {
65-
this.defaultContainerFactory = defaultContainerFactory;
71+
public void setContainerFactoryBeanName(String containerFactoryBeanName) {
72+
this.containerFactoryBeanName = containerFactoryBeanName;
6673
}
6774

6875
/**
69-
* Return the {@link JmsListenerContainerFactory} to use if none has been
70-
* defined for a particular endpoint or {@code null} if no default is set.
76+
* Set the {@link JmsListenerContainerFactory} to use in case a
77+
* {@link JmsListenerEndpoint} is registered with a {@code null} container
78+
* factory.
79+
* <p>Alternatively, the bean name of the {@link JmsListenerContainerFactory}
80+
* to use can be specified for a lazy lookup, see {@see #setContainerFactoryBeanName}
7181
*/
72-
public JmsListenerContainerFactory<?> getDefaultContainerFactory() {
73-
return defaultContainerFactory;
82+
public void setContainerFactory(JmsListenerContainerFactory<?> containerFactory) {
83+
this.containerFactory = containerFactory;
7484
}
7585

7686
/**
@@ -92,6 +102,11 @@ public JmsHandlerMethodFactory getJmsHandlerMethodFactory() {
92102
return jmsHandlerMethodFactory;
93103
}
94104

105+
@Override
106+
public void setApplicationContext(ApplicationContext applicationContext) {
107+
this.applicationContext = applicationContext;
108+
}
109+
95110
/**
96111
* Register a new {@link JmsListenerEndpoint} alongside the {@link JmsListenerContainerFactory}
97112
* to use to create the underlying container.
@@ -109,7 +124,7 @@ public void registerEndpoint(JmsListenerEndpoint endpoint, JmsListenerContainerF
109124
* Register a new {@link JmsListenerEndpoint} using the default {@link JmsListenerContainerFactory}
110125
* to create the underlying container.
111126
*
112-
* @see #setDefaultContainerFactory(JmsListenerContainerFactory)
127+
* @see #setContainerFactory(JmsListenerContainerFactory)
113128
* @see #registerEndpoint(JmsListenerEndpoint, JmsListenerContainerFactory)
114129
*/
115130
public void registerEndpoint(JmsListenerEndpoint endpoint) {
@@ -118,6 +133,7 @@ public void registerEndpoint(JmsListenerEndpoint endpoint) {
118133

119134
@Override
120135
public void afterPropertiesSet() throws Exception {
136+
Assert.notNull(applicationContext, "ApplicationContext must not be null");
121137
startAllEndpoints();
122138
}
123139

@@ -132,8 +148,13 @@ private JmsListenerContainerFactory<?> resolveContainerFactory(JmsListenerEndpoi
132148
if (descriptor.containerFactory != null) {
133149
return descriptor.containerFactory;
134150
}
135-
else if (defaultContainerFactory != null) {
136-
return defaultContainerFactory;
151+
else if (this.containerFactory != null) {
152+
return this.containerFactory;
153+
}
154+
else if (this.containerFactoryBeanName != null) {
155+
this.containerFactory = applicationContext.getBean(
156+
this.containerFactoryBeanName, JmsListenerContainerFactory.class);
157+
return this.containerFactory; // Consider changing this if live change of the factory is required
137158
}
138159
else {
139160
throw new IllegalStateException("Could not resolve the "

spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-4.1.xsd

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
</xsd:appinfo>
4444
</xsd:annotation>
4545
</xsd:attribute>
46-
<xsd:attribute name="default-container-factory" type="xsd:string" use="optional">
46+
<xsd:attribute name="container-factory" type="xsd:string" use="optional">
4747
<xsd:annotation>
4848
<xsd:documentation><![CDATA[
49-
Specifies the default org.springframework.jms.config.JmsListenerContainerFactory instance to
49+
Specifies the org.springframework.jms.config.JmsListenerContainerFactory instance to
5050
use to create the container for a jms listener endpoint that does not define a specific
5151
factory. This permits in practice to omit the "containerFactory" attribute of the JmsListener
52-
annotation. If not provided, each endpoint must define the container factory to use.
52+
annotation. This attribute is not required as each endpoint may define the factory to use and,
53+
as a convenience, the JmsListenerContainerFactory with name 'jmsListenerContainerFactory' is
54+
looked up by default.
5355
]]></xsd:documentation>
5456
<xsd:appinfo>
5557
<tool:annotation kind="ref">

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,21 @@ public abstract class AbstractJmsAnnotationDrivenTests {
5959
public abstract void customConfiguration();
6060

6161
@Test
62-
public abstract void defaultContainerFactoryConfiguration();
62+
public abstract void explicitContainerFactory();
63+
64+
@Test
65+
public abstract void defaultContainerFactory();
6366

6467
@Test
6568
public abstract void jmsHandlerMethodFactoryConfiguration() throws JMSException;
6669

6770
/**
68-
* Test for {@link SampleBean} discovery.
71+
* Test for {@link SampleBean} discovery. If a factory with the default name
72+
* is set, an endpoint will use it automatically
6973
*/
7074
public void testSampleConfiguration(ApplicationContext context) {
7175
JmsListenerContainerTestFactory defaultFactory =
72-
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
76+
context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class);
7377
JmsListenerContainerTestFactory simpleFactory =
7478
context.getBean("simpleFactory", JmsListenerContainerTestFactory.class);
7579
assertEquals(1, defaultFactory.getContainers().size());
@@ -79,7 +83,7 @@ public void testSampleConfiguration(ApplicationContext context) {
7983
@Component
8084
static class SampleBean {
8185

82-
@JmsListener(containerFactory = "defaultFactory", destination = "myQueue")
86+
@JmsListener(destination = "myQueue")
8387
public void defaultHandle(String msg) {
8488
}
8589

@@ -89,7 +93,9 @@ public void simpleHandle(String msg) {
8993
}
9094

9195
/**
92-
* Test for {@link FullBean} discovery.
96+
* Test for {@link FullBean} discovery. In this case, no default is set because
97+
* all endpoints provide a default registry. This shows that the default factory
98+
* is only retrieved if it needs to be.
9399
*/
94100
public void testFullConfiguration(ApplicationContext context) {
95101
JmsListenerContainerTestFactory simpleFactory =
@@ -116,11 +122,12 @@ public String fullHandle(String msg) {
116122

117123
/**
118124
* Test for {@link CustomBean} and an manually endpoint registered
119-
* with "myCustomEndpointId".
125+
* with "myCustomEndpointId". The custom endpoint does not provide
126+
* any factory so it's registered with the default one
120127
*/
121128
public void testCustomConfiguration(ApplicationContext context) {
122129
JmsListenerContainerTestFactory defaultFactory =
123-
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
130+
context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class);
124131
JmsListenerContainerTestFactory customFactory =
125132
context.getBean("customFactory", JmsListenerContainerTestFactory.class);
126133
assertEquals(1, defaultFactory.getContainers().size());
@@ -150,11 +157,22 @@ public void customHandle(String msg) {
150157

151158
/**
152159
* Test for {@link DefaultBean} that does not define the container
153-
* factory to use as a default is registered.
160+
* factory to use as a default is registered with an explicit
161+
* default.
162+
*/
163+
public void testExplicitContainerFactoryConfiguration(ApplicationContext context) {
164+
JmsListenerContainerTestFactory defaultFactory =
165+
context.getBean("simpleFactory", JmsListenerContainerTestFactory.class);
166+
assertEquals(1, defaultFactory.getContainers().size());
167+
}
168+
169+
/**
170+
* Test for {@link DefaultBean} that does not define the container
171+
* factory to use as a default is registered with the default name.
154172
*/
155173
public void testDefaultContainerFactoryConfiguration(ApplicationContext context) {
156174
JmsListenerContainerTestFactory defaultFactory =
157-
context.getBean("defaultFactory", JmsListenerContainerTestFactory.class);
175+
context.getBean("jmsListenerContainerFactory", JmsListenerContainerTestFactory.class);
158176
assertEquals(1, defaultFactory.getContainers().size());
159177
}
160178

0 commit comments

Comments
 (0)