Skip to content

Commit 739f648

Browse files
authored
GH-6656: Add JmsTemplate overloads to JMS DSL channel specs
Fixes: #6656 - Allow `JmsChannelFactoryBean` to accept an externally provided `JmsTemplate` - Add `Jms.pollableChannel(JmsTemplate)` and `Jms.pollableChannel(String, JmsTemplate)` factory methods - Add `Jms.channel(JmsTemplate)` factory method - Add protected constructors to `JmsPollableMessageChannelSpec` and `JmsMessageChannelSpec` accepting `JmsTemplate` - Add integration test verifying the polling flow with a `JmsTemplate` * Guard `JmsChannelFactoryBean` against mixed template config Add a validation guard to `JmsChannelFactoryBean` that prevents users from mixing an externally provided `JmsTemplate` with individual template configuration options set on the factory bean itself. Update the What's New documentation to describe the new `JmsTemplate` support in the JMS DSL. * Simplify error message when setting properties If a user sets an external JmsTemplate, they can not set the properties via the factory. This PR simplifies the logic behind this determination
1 parent 2c44e81 commit 739f648

7 files changed

Lines changed: 209 additions & 4 deletions

File tree

spring-integration-jms/src/main/java/org/springframework/integration/jms/config/JmsChannelFactoryBean.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
* @author Oleg Zhurakousky
5656
* @author Gary Russell
5757
* @author Artem Bilan
58+
* @author Glenn Renfro
5859
*
5960
* @since 2.0
6061
*/
@@ -68,7 +69,11 @@ public class JmsChannelFactoryBean extends AbstractFactoryBean<AbstractJmsChanne
6869

6970
private final boolean messageDriven;
7071

71-
private final JmsTemplate jmsTemplate = new DynamicJmsTemplate();
72+
private JmsTemplate jmsTemplate = new DynamicJmsTemplate();
73+
74+
private boolean jmsTemplateExplicitlySet;
75+
76+
private boolean defaultTemplatePropertySet;
7277

7378
private @Nullable Class<? extends AbstractMessageListenerContainer> containerType;
7479

@@ -164,30 +169,37 @@ public void setInterceptors(List<ChannelInterceptor> interceptors) {
164169
*/
165170

166171
public void setDeliveryPersistent(boolean deliveryPersistent) {
172+
recordJmsTemplateOption();
167173
this.jmsTemplate.setDeliveryPersistent(deliveryPersistent);
168174
}
169175

170176
public void setExplicitQosEnabled(boolean explicitQosEnabled) {
177+
recordJmsTemplateOption();
171178
this.jmsTemplate.setExplicitQosEnabled(explicitQosEnabled);
172179
}
173180

174181
public void setMessageConverter(MessageConverter messageConverter) {
182+
recordJmsTemplateOption();
175183
this.jmsTemplate.setMessageConverter(messageConverter);
176184
}
177185

178186
public void setMessageIdEnabled(boolean messageIdEnabled) {
187+
recordJmsTemplateOption();
179188
this.jmsTemplate.setMessageIdEnabled(messageIdEnabled);
180189
}
181190

182191
public void setMessageTimestampEnabled(boolean messageTimestampEnabled) {
192+
recordJmsTemplateOption();
183193
this.jmsTemplate.setMessageTimestampEnabled(messageTimestampEnabled);
184194
}
185195

186196
public void setPriority(int priority) {
197+
recordJmsTemplateOption();
187198
this.jmsTemplate.setPriority(priority);
188199
}
189200

190201
public void setTimeToLive(long timeToLive) {
202+
recordJmsTemplateOption();
191203
this.jmsTemplate.setTimeToLive(timeToLive);
192204
}
193205

@@ -236,6 +248,7 @@ public void setConcurrentConsumers(int concurrentConsumers) {
236248

237249
public void setConnectionFactory(ConnectionFactory connectionFactory) {
238250
this.connectionFactory = connectionFactory;
251+
recordJmsTemplateOption();
239252
this.jmsTemplate.setConnectionFactory(this.connectionFactory);
240253
}
241254

@@ -254,6 +267,7 @@ public void setDestinationName(String destinationName) {
254267

255268
public void setDestinationResolver(DestinationResolver destinationResolver) {
256269
this.destinationResolver = destinationResolver;
270+
recordJmsTemplateOption();
257271
this.jmsTemplate.setDestinationResolver(destinationResolver);
258272
}
259273

@@ -302,16 +316,19 @@ public void setPhase(int phase) {
302316

303317
public void setPubSubDomain(boolean pubSubDomain) {
304318
this.pubSubDomain = pubSubDomain;
319+
recordJmsTemplateOption();
305320
this.jmsTemplate.setPubSubDomain(pubSubDomain);
306321
}
307322

308323
public void setPubSubNoLocal(boolean pubSubNoLocal) {
309324
this.pubSubNoLocal = pubSubNoLocal;
325+
recordJmsTemplateOption();
310326
this.jmsTemplate.setPubSubNoLocal(pubSubNoLocal);
311327
}
312328

313329
public void setReceiveTimeout(long receiveTimeout) {
314330
this.receiveTimeout = receiveTimeout;
331+
recordJmsTemplateOption();
315332
this.jmsTemplate.setReceiveTimeout(receiveTimeout);
316333
}
317334

@@ -322,11 +339,30 @@ public void setRecoveryInterval(long recoveryInterval) {
322339

323340
public void setSessionAcknowledgeMode(int sessionAcknowledgeMode) {
324341
this.sessionAcknowledgeMode = sessionAcknowledgeMode;
342+
recordJmsTemplateOption();
325343
this.jmsTemplate.setSessionAcknowledgeMode(sessionAcknowledgeMode);
326344
}
327345

346+
/**
347+
* The template to be used by the Factory.
348+
* When an external {@link JmsTemplate} is provided, none of the individual template options
349+
* (e.g. {@code deliveryPersistent}, {@code connectionFactory}, {@code receiveTimeout}, etc.)
350+
* may be set on this factory bean — configure them directly on the provided template instead.
351+
* @param jmsTemplate the {@link JmsTemplate} to build on
352+
* @since 7.1
353+
*/
354+
public void setJmsTemplate(JmsTemplate jmsTemplate) {
355+
this.jmsTemplate = jmsTemplate;
356+
this.jmsTemplateExplicitlySet = true;
357+
}
358+
359+
private void recordJmsTemplateOption() {
360+
this.defaultTemplatePropertySet = true;
361+
}
362+
328363
public void setSessionTransacted(boolean sessionTransacted) {
329364
this.sessionTransacted = sessionTransacted;
365+
recordJmsTemplateOption();
330366
this.jmsTemplate.setSessionTransacted(sessionTransacted);
331367
}
332368

@@ -406,6 +442,9 @@ protected AbstractJmsChannel createInstance() {
406442
private void initializeJmsTemplate() {
407443
Assert.isTrue(this.destination != null ^ this.destinationName != null,
408444
"Exactly one of destination or destinationName is required.");
445+
Assert.isTrue(!this.jmsTemplateExplicitlySet || !this.defaultTemplatePropertySet,
446+
() -> "JmsTemplate properties must be configured on the externally supplied JmsTemplate, " +
447+
"not on the factory bean.");
409448
JavaUtils.INSTANCE
410449
.acceptIfNotNull(this.destination, this.jmsTemplate::setDefaultDestination)
411450
.acceptIfNotNull(this.destinationName, this.jmsTemplate::setDefaultDestinationName);

spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/Jms.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Artem Bilan
3131
* @author Gary Russell
3232
* @author Artem Vozhdayenko
33+
* @author Glenn Renfro
3334
*
3435
* @since 5.0
3536
*/
@@ -46,6 +47,18 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
4647
return new JmsPollableMessageChannelSpec<>(connectionFactory);
4748
}
4849

50+
/**
51+
* The factory to produce a {@link JmsPollableMessageChannelSpec}.
52+
* @param jmsTemplate the {@link JmsTemplate} to build on
53+
* @return the {@link JmsPollableMessageChannelSpec} instance
54+
* @since 7.1
55+
*/
56+
public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChannel(
57+
JmsTemplate jmsTemplate) {
58+
59+
return new JmsPollableMessageChannelSpec<>(jmsTemplate);
60+
}
61+
4962
/**
5063
* The factory to produce a {@link JmsPollableMessageChannelSpec}.
5164
* @param id the bean name for the target {@code PollableChannel} component
@@ -60,6 +73,20 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
6073
return spec.id(id);
6174
}
6275

76+
/**
77+
* The template to produce a {@link JmsPollableMessageChannelSpec}.
78+
* @param id the bean name for the target {@code PollableChannel} component
79+
* @param jmsTemplate the {@link JmsTemplate} to build on
80+
* @return the {@link JmsPollableMessageChannelSpec} instance
81+
* @since 7.1
82+
*/
83+
public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChannel(String id,
84+
JmsTemplate jmsTemplate) {
85+
86+
JmsPollableMessageChannelSpec<?, PollableJmsChannel> spec = new JmsPollableMessageChannelSpec<>(jmsTemplate);
87+
return spec.id(id);
88+
}
89+
6390
/**
6491
* The factory to produce a {@link JmsMessageChannelSpec}.
6592
* @param connectionFactory the JMS ConnectionFactory to build on
@@ -69,6 +96,16 @@ public static JmsPollableMessageChannelSpec<?, PollableJmsChannel> pollableChann
6996
return new JmsMessageChannelSpec<>(connectionFactory);
7097
}
7198

99+
/**
100+
* The factory to produce a {@link JmsMessageChannelSpec}.
101+
* @param jmsTemplate the {@link JmsTemplate} to build on
102+
* @return the {@link JmsMessageChannelSpec} instance
103+
* @since 7.1
104+
*/
105+
public static JmsMessageChannelSpec<?, ?> channel(JmsTemplate jmsTemplate) {
106+
return new JmsMessageChannelSpec<>(jmsTemplate);
107+
}
108+
72109
/**
73110
* The factory to produce a {@link JmsMessageChannelSpec}.
74111
* @param id the bean name for the target {@code MessageChannel} component

spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsMessageChannelSpec.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.integration.jms.channel.AbstractJmsChannel;
2424
import org.springframework.integration.jms.channel.SubscribableJmsChannel;
2525
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
26+
import org.springframework.jms.core.JmsTemplate;
2627
import org.springframework.jms.listener.AbstractMessageListenerContainer;
2728
import org.springframework.transaction.PlatformTransactionManager;
2829
import org.springframework.util.ErrorHandler;
@@ -37,6 +38,7 @@
3738
* @author Artem Bilan
3839
* @author Gary Russell
3940
* @author Artem Vozhdayenko
41+
* @author Glenn Renfro
4042
*
4143
* @since 5.0
4244
*/
@@ -47,6 +49,10 @@ protected JmsMessageChannelSpec(ConnectionFactory connectionFactory) {
4749
super(new JmsChannelFactoryBean(true), connectionFactory);
4850
}
4951

52+
protected JmsMessageChannelSpec(JmsTemplate jmsTemplate) {
53+
super(new JmsChannelFactoryBean(true), jmsTemplate);
54+
}
55+
5056
/**
5157
* Configure the type of the container.
5258
* {@link AbstractMessageListenerContainer}. Defaults to

spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsPollableMessageChannelSpec.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.integration.dsl.MessageChannelSpec;
2525
import org.springframework.integration.jms.channel.AbstractJmsChannel;
2626
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
27+
import org.springframework.jms.core.JmsTemplate;
2728
import org.springframework.jms.support.converter.MessageConverter;
2829
import org.springframework.jms.support.destination.DestinationResolver;
2930

@@ -36,6 +37,7 @@
3637
* @author Artem Bilan
3738
* @author Gary Russell
3839
* @author Artem Vozhdayenko
40+
* @author Glenn Renfro
3941
*
4042
* @since 5.0
4143
*/
@@ -48,13 +50,22 @@ protected JmsPollableMessageChannelSpec(ConnectionFactory connectionFactory) {
4850
this(new JmsChannelFactoryBean(false), connectionFactory);
4951
}
5052

53+
protected JmsPollableMessageChannelSpec(JmsTemplate jmsTemplate) {
54+
this(new JmsChannelFactoryBean(false), jmsTemplate);
55+
}
56+
5157
protected JmsPollableMessageChannelSpec(JmsChannelFactoryBean jmsChannelFactoryBean,
5258
ConnectionFactory connectionFactory) {
5359

5460
this.jmsChannelFactoryBean = jmsChannelFactoryBean;
5561
this.jmsChannelFactoryBean.setConnectionFactory(connectionFactory);
56-
this.jmsChannelFactoryBean.setSingleton(false);
57-
this.jmsChannelFactoryBean.setBeanFactory(new DefaultListableBeanFactory());
62+
}
63+
64+
protected JmsPollableMessageChannelSpec(JmsChannelFactoryBean jmsChannelFactoryBean,
65+
JmsTemplate jmsTemplate) {
66+
67+
this.jmsChannelFactoryBean = jmsChannelFactoryBean;
68+
this.jmsChannelFactoryBean.setJmsTemplate(jmsTemplate);
5869
}
5970

6071
@Override
@@ -220,6 +231,8 @@ public S sessionTransacted(boolean sessionTransacted) {
220231
@SuppressWarnings("unchecked")
221232
protected T doGet() {
222233
try {
234+
this.jmsChannelFactoryBean.setSingleton(false);
235+
this.jmsChannelFactoryBean.setBeanFactory(new DefaultListableBeanFactory());
223236
this.channel = (T) this.jmsChannelFactoryBean.getObject();
224237
}
225238
catch (Exception e) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2026-present 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.integration.jms.config;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.support.StaticListableBeanFactory;
22+
import org.springframework.jms.core.JmsTemplate;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26+
27+
/**
28+
* @author Glenn Renfro
29+
*/
30+
31+
class JmsChannelFactoryBeanTests {
32+
33+
@Test
34+
void optionSetBeforeExternalTemplateAlsoThrows() {
35+
JmsChannelFactoryBean fb = new JmsChannelFactoryBean(false);
36+
fb.setBeanName("testChannel");
37+
fb.setDestinationName("testQueue");
38+
fb.setSessionTransacted(true);
39+
fb.setJmsTemplate(new JmsTemplate());
40+
fb.setBeanFactory(new StaticListableBeanFactory());
41+
42+
assertThatIllegalArgumentException()
43+
.isThrownBy(fb::createInstance)
44+
.withMessageContaining("JmsTemplate properties must be configured on the externally supplied " +
45+
"JmsTemplate, not on the factory bean.");
46+
}
47+
48+
@Test
49+
void externalTemplateAloneDoesNotThrowGuard() {
50+
JmsChannelFactoryBean fb = new JmsChannelFactoryBean(false);
51+
fb.setBeanName("testChannel");
52+
fb.setJmsTemplate(new JmsTemplate());
53+
fb.setDestinationName("testQueue");
54+
fb.setBeanFactory(new StaticListableBeanFactory());
55+
assertThat(fb.createInstance()).isNotNull();
56+
}
57+
58+
}

0 commit comments

Comments
 (0)