Skip to content

Commit c71222e

Browse files
committed
Add support for auto-configuring SimpleMessageListenerContainer
This commit introduces a new `spring.jms.listener.container-type` configuration property which can be used to auto-configure JMS listener support backend by `SimpleMessageListenerContainer` instead of `DefaultMessageListenerContainer`. Signed-off-by: Vedran Pavic <[email protected]>
1 parent 8eb8d3f commit c71222e

File tree

6 files changed

+296
-118
lines changed

6 files changed

+296
-118
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2012-2025 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+
* https://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.boot.autoconfigure.jms;
18+
19+
import io.micrometer.observation.ObservationRegistry;
20+
import jakarta.jms.ConnectionFactory;
21+
import jakarta.jms.ExceptionListener;
22+
23+
import org.springframework.boot.autoconfigure.jms.JmsProperties.Listener.Session;
24+
import org.springframework.boot.context.properties.PropertyMapper;
25+
import org.springframework.jms.config.AbstractJmsListenerContainerFactory;
26+
import org.springframework.jms.support.converter.MessageConverter;
27+
import org.springframework.jms.support.destination.DestinationResolver;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* Configures {@link AbstractJmsListenerContainerFactory} with sensible defaults.
32+
*
33+
* @param <T> the connection factory type.
34+
* @author Vedran Pavic
35+
* @since 3.5.0
36+
*/
37+
public abstract class AbstractJmsListenerContainerFactoryConfigurer<T extends AbstractJmsListenerContainerFactory<?>> {
38+
39+
private DestinationResolver destinationResolver;
40+
41+
private MessageConverter messageConverter;
42+
43+
private ExceptionListener exceptionListener;
44+
45+
private ObservationRegistry observationRegistry;
46+
47+
private JmsProperties jmsProperties;
48+
49+
/**
50+
* Set the {@link DestinationResolver} to use or {@code null} if no destination
51+
* resolver should be associated with the factory by default.
52+
* @param destinationResolver the {@link DestinationResolver}
53+
*/
54+
void setDestinationResolver(DestinationResolver destinationResolver) {
55+
this.destinationResolver = destinationResolver;
56+
}
57+
58+
/**
59+
* Set the {@link MessageConverter} to use or {@code null} if the out-of-the-box
60+
* converter should be used.
61+
* @param messageConverter the {@link MessageConverter}
62+
*/
63+
void setMessageConverter(MessageConverter messageConverter) {
64+
this.messageConverter = messageConverter;
65+
}
66+
67+
/**
68+
* Set the {@link ExceptionListener} to use or {@code null} if no exception listener
69+
* should be associated by default.
70+
* @param exceptionListener the {@link ExceptionListener}
71+
*/
72+
void setExceptionListener(ExceptionListener exceptionListener) {
73+
this.exceptionListener = exceptionListener;
74+
}
75+
76+
/**
77+
* Set the {@link ObservationRegistry} to use.
78+
* @param observationRegistry the {@link ObservationRegistry}
79+
*/
80+
void setObservationRegistry(ObservationRegistry observationRegistry) {
81+
this.observationRegistry = observationRegistry;
82+
}
83+
84+
/**
85+
* Set the {@link JmsProperties} to use.
86+
* @param jmsProperties the {@link JmsProperties}
87+
*/
88+
void setJmsProperties(JmsProperties jmsProperties) {
89+
this.jmsProperties = jmsProperties;
90+
}
91+
92+
/**
93+
* Configure the specified jms listener container factory. The factory can be further
94+
* tuned and default settings can be overridden.
95+
* @param factory the {@link AbstractJmsListenerContainerFactory} instance to
96+
* configure
97+
* @param connectionFactory the {@link ConnectionFactory} to use
98+
*/
99+
public void configure(T factory, ConnectionFactory connectionFactory) {
100+
Assert.notNull(factory, "'factory' must not be null");
101+
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
102+
JmsProperties.Listener listenerProperties = this.jmsProperties.getListener();
103+
Session sessionProperties = listenerProperties.getSession();
104+
factory.setConnectionFactory(connectionFactory);
105+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
106+
map.from(this.jmsProperties::isPubSubDomain).to(factory::setPubSubDomain);
107+
map.from(this.jmsProperties::isSubscriptionDurable).to(factory::setSubscriptionDurable);
108+
map.from(this.jmsProperties::getClientId).to(factory::setClientId);
109+
map.from(this.destinationResolver).to(factory::setDestinationResolver);
110+
map.from(this.messageConverter).to(factory::setMessageConverter);
111+
map.from(this.exceptionListener).to(factory::setExceptionListener);
112+
map.from(sessionProperties.getAcknowledgeMode()::getMode).to(factory::setSessionAcknowledgeMode);
113+
map.from(this.observationRegistry).to(factory::setObservationRegistry);
114+
map.from(sessionProperties::getTransacted).to(factory::setSessionTransacted);
115+
map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup);
116+
configure(factory, connectionFactory, this.jmsProperties);
117+
}
118+
119+
/**
120+
* Configures the given {@code factory} using the given {@code connectionFactory} and
121+
* {@code jmsProperties}.
122+
* @param factory the {@link AbstractJmsListenerContainerFactory} instance to
123+
* configure
124+
* @param connectionFactory the {@link ConnectionFactory} to use
125+
* @param jmsProperties the {@link JmsProperties} to use
126+
*/
127+
protected abstract void configure(T factory, ConnectionFactory connectionFactory, JmsProperties jmsProperties);
128+
129+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java

+10-74
Original file line numberDiff line numberDiff line change
@@ -20,66 +20,26 @@
2020

2121
import io.micrometer.observation.ObservationRegistry;
2222
import jakarta.jms.ConnectionFactory;
23-
import jakarta.jms.ExceptionListener;
2423

2524
import org.springframework.boot.autoconfigure.jms.JmsProperties.Listener.Session;
2625
import org.springframework.boot.context.properties.PropertyMapper;
2726
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
28-
import org.springframework.jms.support.converter.MessageConverter;
29-
import org.springframework.jms.support.destination.DestinationResolver;
3027
import org.springframework.transaction.jta.JtaTransactionManager;
31-
import org.springframework.util.Assert;
3228

3329
/**
34-
* Configure {@link DefaultJmsListenerContainerFactory} with sensible defaults.
30+
* Configures {@link DefaultJmsListenerContainerFactory} with sensible defaults.
3531
*
3632
* @author Stephane Nicoll
3733
* @author Eddú Meléndez
3834
* @author Vedran Pavic
3935
* @author Lasse Wulff
4036
* @since 1.3.3
4137
*/
42-
public final class DefaultJmsListenerContainerFactoryConfigurer {
43-
44-
private DestinationResolver destinationResolver;
45-
46-
private MessageConverter messageConverter;
47-
48-
private ExceptionListener exceptionListener;
38+
public final class DefaultJmsListenerContainerFactoryConfigurer
39+
extends AbstractJmsListenerContainerFactoryConfigurer<DefaultJmsListenerContainerFactory> {
4940

5041
private JtaTransactionManager transactionManager;
5142

52-
private JmsProperties jmsProperties;
53-
54-
private ObservationRegistry observationRegistry;
55-
56-
/**
57-
* Set the {@link DestinationResolver} to use or {@code null} if no destination
58-
* resolver should be associated with the factory by default.
59-
* @param destinationResolver the {@link DestinationResolver}
60-
*/
61-
void setDestinationResolver(DestinationResolver destinationResolver) {
62-
this.destinationResolver = destinationResolver;
63-
}
64-
65-
/**
66-
* Set the {@link MessageConverter} to use or {@code null} if the out-of-the-box
67-
* converter should be used.
68-
* @param messageConverter the {@link MessageConverter}
69-
*/
70-
void setMessageConverter(MessageConverter messageConverter) {
71-
this.messageConverter = messageConverter;
72-
}
73-
74-
/**
75-
* Set the {@link ExceptionListener} to use or {@code null} if no exception listener
76-
* should be associated by default.
77-
* @param exceptionListener the {@link ExceptionListener}
78-
*/
79-
void setExceptionListener(ExceptionListener exceptionListener) {
80-
this.exceptionListener = exceptionListener;
81-
}
82-
8343
/**
8444
* Set the {@link JtaTransactionManager} to use or {@code null} if the JTA support
8545
* should not be used.
@@ -89,50 +49,26 @@ void setTransactionManager(JtaTransactionManager transactionManager) {
8949
this.transactionManager = transactionManager;
9050
}
9151

92-
/**
93-
* Set the {@link JmsProperties} to use.
94-
* @param jmsProperties the {@link JmsProperties}
95-
*/
96-
void setJmsProperties(JmsProperties jmsProperties) {
97-
this.jmsProperties = jmsProperties;
98-
}
99-
10052
/**
10153
* Set the {@link ObservationRegistry} to use.
10254
* @param observationRegistry the {@link ObservationRegistry}
10355
* @since 3.2.1
10456
*/
57+
@Override
10558
public void setObservationRegistry(ObservationRegistry observationRegistry) {
106-
this.observationRegistry = observationRegistry;
59+
super.setObservationRegistry(observationRegistry);
10760
}
10861

109-
/**
110-
* Configure the specified jms listener container factory. The factory can be further
111-
* tuned and default settings can be overridden.
112-
* @param factory the {@link DefaultJmsListenerContainerFactory} instance to configure
113-
* @param connectionFactory the {@link ConnectionFactory} to use
114-
*/
115-
public void configure(DefaultJmsListenerContainerFactory factory, ConnectionFactory connectionFactory) {
116-
Assert.notNull(factory, "'factory' must not be null");
117-
Assert.notNull(connectionFactory, "'connectionFactory' must not be null");
118-
JmsProperties.Listener listenerProperties = this.jmsProperties.getListener();
119-
Session sessionProperties = listenerProperties.getSession();
120-
factory.setConnectionFactory(connectionFactory);
62+
@Override
63+
protected void configure(DefaultJmsListenerContainerFactory factory, ConnectionFactory connectionFactory,
64+
JmsProperties jmsProperties) {
12165
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
122-
map.from(this.jmsProperties::isPubSubDomain).to(factory::setPubSubDomain);
123-
map.from(this.jmsProperties::isSubscriptionDurable).to(factory::setSubscriptionDurable);
124-
map.from(this.jmsProperties::getClientId).to(factory::setClientId);
66+
JmsProperties.Listener listenerProperties = jmsProperties.getListener();
67+
Session sessionProperties = listenerProperties.getSession();
12568
map.from(this.transactionManager).to(factory::setTransactionManager);
126-
map.from(this.destinationResolver).to(factory::setDestinationResolver);
127-
map.from(this.messageConverter).to(factory::setMessageConverter);
128-
map.from(this.exceptionListener).to(factory::setExceptionListener);
129-
map.from(sessionProperties.getAcknowledgeMode()::getMode).to(factory::setSessionAcknowledgeMode);
13069
if (this.transactionManager == null && sessionProperties.getTransacted() == null) {
13170
factory.setSessionTransacted(true);
13271
}
133-
map.from(this.observationRegistry).to(factory::setObservationRegistry);
134-
map.from(sessionProperties::getTransacted).to(factory::setSessionTransacted);
135-
map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup);
13672
map.from(listenerProperties::formatConcurrency).to(factory::setConcurrency);
13773
map.from(listenerProperties::getReceiveTimeout).as(Duration::toMillis).to(factory::setReceiveTimeout);
13874
map.from(listenerProperties::getMaxMessagesPerTask).to(factory::setMaxMessagesPerTask);

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java

+59-39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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.
@@ -24,13 +24,15 @@
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
2829
import org.springframework.boot.jms.ConnectionFactoryUnwrapper;
2930
import org.springframework.context.annotation.Bean;
3031
import org.springframework.context.annotation.Configuration;
3132
import org.springframework.jms.annotation.EnableJms;
3233
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
3334
import org.springframework.jms.config.JmsListenerConfigUtils;
35+
import org.springframework.jms.config.SimpleJmsListenerContainerFactory;
3436
import org.springframework.jms.support.converter.MessageConverter;
3537
import org.springframework.jms.support.destination.DestinationResolver;
3638
import org.springframework.jms.support.destination.JndiDestinationResolver;
@@ -42,56 +44,74 @@
4244
* @author Phillip Webb
4345
* @author Stephane Nicoll
4446
* @author Eddú Meléndez
47+
* @author Vedran Pavic
4548
*/
4649
@Configuration(proxyBeanMethods = false)
4750
@ConditionalOnClass(EnableJms.class)
4851
class JmsAnnotationDrivenConfiguration {
4952

50-
private final ObjectProvider<DestinationResolver> destinationResolver;
51-
52-
private final ObjectProvider<JtaTransactionManager> transactionManager;
53+
@Configuration(proxyBeanMethods = false)
54+
@ConditionalOnProperty(name = "spring.jms.listener.container-type", havingValue = "default", matchIfMissing = true)
55+
static class DefaultJmsListenerContainerFactoryConfiguration {
5356

54-
private final ObjectProvider<MessageConverter> messageConverter;
57+
@Bean
58+
@ConditionalOnMissingBean
59+
DefaultJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigurer(
60+
ObjectProvider<DestinationResolver> destinationResolver,
61+
ObjectProvider<JtaTransactionManager> transactionManager,
62+
ObjectProvider<MessageConverter> messageConverter, ObjectProvider<ExceptionListener> exceptionListener,
63+
ObjectProvider<ObservationRegistry> observationRegistry, JmsProperties properties) {
64+
DefaultJmsListenerContainerFactoryConfigurer configurer = new DefaultJmsListenerContainerFactoryConfigurer();
65+
configurer.setDestinationResolver(destinationResolver.getIfUnique());
66+
configurer.setTransactionManager(transactionManager.getIfUnique());
67+
configurer.setMessageConverter(messageConverter.getIfUnique());
68+
configurer.setExceptionListener(exceptionListener.getIfUnique());
69+
configurer.setObservationRegistry(observationRegistry.getIfUnique());
70+
configurer.setJmsProperties(properties);
71+
return configurer;
72+
}
5573

56-
private final ObjectProvider<ExceptionListener> exceptionListener;
74+
@Bean
75+
@ConditionalOnSingleCandidate(ConnectionFactory.class)
76+
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
77+
DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
78+
DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
79+
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
80+
configurer.configure(factory, ConnectionFactoryUnwrapper.unwrapCaching(connectionFactory));
81+
return factory;
82+
}
5783

58-
private final ObjectProvider<ObservationRegistry> observationRegistry;
84+
}
5985

60-
private final JmsProperties properties;
86+
@Configuration(proxyBeanMethods = false)
87+
@ConditionalOnProperty(name = "spring.jms.listener.container-type", havingValue = "simple")
88+
static class SimpleJmsListenerContainerFactoryConfiguration {
6189

62-
JmsAnnotationDrivenConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
63-
ObjectProvider<JtaTransactionManager> transactionManager, ObjectProvider<MessageConverter> messageConverter,
64-
ObjectProvider<ExceptionListener> exceptionListener,
65-
ObjectProvider<ObservationRegistry> observationRegistry, JmsProperties properties) {
66-
this.destinationResolver = destinationResolver;
67-
this.transactionManager = transactionManager;
68-
this.messageConverter = messageConverter;
69-
this.exceptionListener = exceptionListener;
70-
this.observationRegistry = observationRegistry;
71-
this.properties = properties;
72-
}
90+
@Bean
91+
@ConditionalOnMissingBean
92+
SimpleJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigurer(
93+
ObjectProvider<DestinationResolver> destinationResolver,
94+
ObjectProvider<MessageConverter> messageConverter, ObjectProvider<ExceptionListener> exceptionListener,
95+
ObjectProvider<ObservationRegistry> observationRegistry, JmsProperties properties) {
96+
SimpleJmsListenerContainerFactoryConfigurer configurer = new SimpleJmsListenerContainerFactoryConfigurer();
97+
configurer.setDestinationResolver(destinationResolver.getIfUnique());
98+
configurer.setMessageConverter(messageConverter.getIfUnique());
99+
configurer.setExceptionListener(exceptionListener.getIfUnique());
100+
configurer.setObservationRegistry(observationRegistry.getIfUnique());
101+
configurer.setJmsProperties(properties);
102+
return configurer;
103+
}
73104

74-
@Bean
75-
@ConditionalOnMissingBean
76-
DefaultJmsListenerContainerFactoryConfigurer jmsListenerContainerFactoryConfigurer() {
77-
DefaultJmsListenerContainerFactoryConfigurer configurer = new DefaultJmsListenerContainerFactoryConfigurer();
78-
configurer.setDestinationResolver(this.destinationResolver.getIfUnique());
79-
configurer.setTransactionManager(this.transactionManager.getIfUnique());
80-
configurer.setMessageConverter(this.messageConverter.getIfUnique());
81-
configurer.setExceptionListener(this.exceptionListener.getIfUnique());
82-
configurer.setObservationRegistry(this.observationRegistry.getIfUnique());
83-
configurer.setJmsProperties(this.properties);
84-
return configurer;
85-
}
105+
@Bean
106+
@ConditionalOnSingleCandidate(ConnectionFactory.class)
107+
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
108+
SimpleJmsListenerContainerFactory jmsListenerContainerFactory(
109+
SimpleJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
110+
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
111+
configurer.configure(factory, ConnectionFactoryUnwrapper.unwrapCaching(connectionFactory));
112+
return factory;
113+
}
86114

87-
@Bean
88-
@ConditionalOnSingleCandidate(ConnectionFactory.class)
89-
@ConditionalOnMissingBean(name = "jmsListenerContainerFactory")
90-
DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
91-
DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
92-
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
93-
configurer.configure(factory, ConnectionFactoryUnwrapper.unwrapCaching(connectionFactory));
94-
return factory;
95115
}
96116

97117
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)