From 79e2cb3ec1f13c44a4b299065583c25882acce6f Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 19 Sep 2023 20:23:22 +0200 Subject: [PATCH 1/2] Add config prop for JMS listener's sessionTransacted flag This commit introduces `spring.jms.listener.session-transacted` property in order to enable explicit configuration of `sessionTransacted` on the `DefaultMessageListenerContainer`. Prior to this commit, `sessionTransacted` would be configured implicitly based on presence of `JtaTransactionManager`. See gh-37473 --- ...JmsListenerContainerFactoryConfigurer.java | 10 +++++-- .../boot/autoconfigure/jms/JmsProperties.java | 13 ++++++++ .../jms/JmsAutoConfigurationTests.java | 30 +++++++++++++++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java index 265e00167c..38288acbd4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 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. @@ -32,6 +32,7 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @author Eddú Meléndez + * @author Vedran Pavic * @since 1.3.3 */ public final class DefaultJmsListenerContainerFactoryConfigurer { @@ -101,12 +102,16 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); factory.setConnectionFactory(connectionFactory); factory.setPubSubDomain(this.jmsProperties.isPubSubDomain()); + JmsProperties.Listener listener = this.jmsProperties.getListener(); if (this.transactionManager != null) { factory.setTransactionManager(this.transactionManager); } - else { + else if (listener.getSessionTransacted() == null) { factory.setSessionTransacted(true); } + if (listener.getSessionTransacted() != null) { + factory.setSessionTransacted(listener.getSessionTransacted()); + } if (this.destinationResolver != null) { factory.setDestinationResolver(this.destinationResolver); } @@ -116,7 +121,6 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { if (this.exceptionListener != null) { factory.setExceptionListener(this.exceptionListener); } - JmsProperties.Listener listener = this.jmsProperties.getListener(); factory.setAutoStartup(listener.isAutoStartup()); factory.setSessionAcknowledgeMode(listener.getSession().getAcknowledgeMode().getMode()); String concurrency = listener.formatConcurrency(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java index 91880a571e..46d9a70518 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java @@ -141,6 +141,11 @@ public class JmsProperties { */ private boolean autoStartup = true; + /** + * Whether the container should use transacted JMS sessions. + */ + private Boolean sessionTransacted; + /** * Minimum number of concurrent consumers. When max-concurrency is not specified * the minimum will also be used as the maximum. @@ -180,6 +185,14 @@ public class JmsProperties { this.session.setAcknowledgeMode(acknowledgeMode); } + public Boolean getSessionTransacted() { + return this.sessionTransacted; + } + + public void setSessionTransacted(Boolean sessionTransacted) { + this.sessionTransacted = sessionTransacted; + } + @DeprecatedConfigurationProperty(replacement = "spring.jms.listener.min-concurrency", since = "3.2.0") @Deprecated(since = "3.2.0", forRemoval = true) public Integer getConcurrency() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java index d7cca9e7ce..6283677245 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.mock; * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Eddú Meléndez + * @author Vedran Pavic */ class JmsAutoConfigurationTests { @@ -143,8 +144,9 @@ class JmsAutoConfigurationTests { void testJmsListenerContainerFactoryWithCustomSettings() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) .withPropertyValues("spring.jms.listener.autoStartup=false", - "spring.jms.listener.session.acknowledgeMode=client", "spring.jms.listener.minConcurrency=2", - "spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10") + "spring.jms.listener.session.acknowledgeMode=client", "spring.jms.listener.sessionTransacted=false", + "spring.jms.listener.minConcurrency=2", "spring.jms.listener.receiveTimeout=2s", + "spring.jms.listener.maxConcurrency=10") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -152,6 +154,7 @@ class JmsAutoConfigurationTests { DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory"); assertThat(container.isAutoStartup()).isFalse(); assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE); + assertThat(container.isSessionTransacted()).isFalse(); assertThat(container.getConcurrentConsumers()).isEqualTo(2); assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10); assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L); @@ -179,6 +182,18 @@ class JmsAutoConfigurationTests { }); } + @Test + void testDefaultContainerFactoryWithJtaTransactionManagerAndSessionTransactedEnabled() { + this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.sessionTransacted=true") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isTrue(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", + context.getBean(JtaTransactionManager.class)); + }); + } + @Test void testDefaultContainerFactoryNonJtaTransactionManager() { this.contextRunner.withUserConfiguration(TestConfiguration8.class, EnableJmsConfiguration.class) @@ -198,6 +213,17 @@ class JmsAutoConfigurationTests { }); } + @Test + void testDefaultContainerFactoryNoTransactionManagerAndSessionTransactedDisabled() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .withPropertyValues("spring.jms.listener.sessionTransacted=false") + .run((context) -> { + DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); + assertThat(container.isSessionTransacted()).isFalse(); + assertThat(container).hasFieldOrPropertyWithValue("transactionManager", null); + }); + } + @Test void testDefaultContainerFactoryWithMessageConverters() { this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class, EnableJmsConfiguration.class) From 0d2eaa716cf743879a496078d43267a2fd603190 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 27 Sep 2023 15:06:39 +0100 Subject: [PATCH 2/2] Polish "Add config prop for JMS listener's sessionTransacted flag" See gh-37473 --- ...JmsListenerContainerFactoryConfigurer.java | 11 +++++--- .../boot/autoconfigure/jms/JmsProperties.java | 27 ++++++++++--------- .../jms/JmsAutoConfigurationTests.java | 10 +++---- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java index 38288acbd4..ebbf7e3c56 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/DefaultJmsListenerContainerFactoryConfigurer.java @@ -21,6 +21,7 @@ import java.time.Duration; import jakarta.jms.ConnectionFactory; import jakarta.jms.ExceptionListener; +import org.springframework.boot.autoconfigure.jms.JmsProperties.Listener.Session; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.destination.DestinationResolver; @@ -103,14 +104,16 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { factory.setConnectionFactory(connectionFactory); factory.setPubSubDomain(this.jmsProperties.isPubSubDomain()); JmsProperties.Listener listener = this.jmsProperties.getListener(); + Session session = listener.getSession(); + Boolean sessionTransacted = session.getTransacted(); if (this.transactionManager != null) { factory.setTransactionManager(this.transactionManager); } - else if (listener.getSessionTransacted() == null) { + else if (sessionTransacted == null) { factory.setSessionTransacted(true); } - if (listener.getSessionTransacted() != null) { - factory.setSessionTransacted(listener.getSessionTransacted()); + if (sessionTransacted != null) { + factory.setSessionTransacted(sessionTransacted); } if (this.destinationResolver != null) { factory.setDestinationResolver(this.destinationResolver); @@ -122,7 +125,7 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { factory.setExceptionListener(this.exceptionListener); } factory.setAutoStartup(listener.isAutoStartup()); - factory.setSessionAcknowledgeMode(listener.getSession().getAcknowledgeMode().getMode()); + factory.setSessionAcknowledgeMode(session.getAcknowledgeMode().getMode()); String concurrency = listener.formatConcurrency(); if (concurrency != null) { factory.setConcurrency(concurrency); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java index 46d9a70518..cc6d314069 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsProperties.java @@ -141,11 +141,6 @@ public class JmsProperties { */ private boolean autoStartup = true; - /** - * Whether the container should use transacted JMS sessions. - */ - private Boolean sessionTransacted; - /** * Minimum number of concurrent consumers. When max-concurrency is not specified * the minimum will also be used as the maximum. @@ -185,14 +180,6 @@ public class JmsProperties { this.session.setAcknowledgeMode(acknowledgeMode); } - public Boolean getSessionTransacted() { - return this.sessionTransacted; - } - - public void setSessionTransacted(Boolean sessionTransacted) { - this.sessionTransacted = sessionTransacted; - } - @DeprecatedConfigurationProperty(replacement = "spring.jms.listener.min-concurrency", since = "3.2.0") @Deprecated(since = "3.2.0", forRemoval = true) public Integer getConcurrency() { @@ -247,6 +234,12 @@ public class JmsProperties { */ private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO; + /** + * Whether the listener container should use transacted JMS sessions. Defaults + * to false in the presence of a JtaTransactionManager and true otherwise. + */ + private Boolean transacted; + public AcknowledgeMode getAcknowledgeMode() { return this.acknowledgeMode; } @@ -255,6 +248,14 @@ public class JmsProperties { this.acknowledgeMode = acknowledgeMode; } + public Boolean getTransacted() { + return this.transacted; + } + + public void setTransacted(Boolean transacted) { + this.transacted = transacted; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java index 6283677245..49f5ddb3b2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java @@ -144,9 +144,9 @@ class JmsAutoConfigurationTests { void testJmsListenerContainerFactoryWithCustomSettings() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) .withPropertyValues("spring.jms.listener.autoStartup=false", - "spring.jms.listener.session.acknowledgeMode=client", "spring.jms.listener.sessionTransacted=false", - "spring.jms.listener.minConcurrency=2", "spring.jms.listener.receiveTimeout=2s", - "spring.jms.listener.maxConcurrency=10") + "spring.jms.listener.session.acknowledgeMode=client", + "spring.jms.listener.session.transacted=false", "spring.jms.listener.minConcurrency=2", + "spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -185,7 +185,7 @@ class JmsAutoConfigurationTests { @Test void testDefaultContainerFactoryWithJtaTransactionManagerAndSessionTransactedEnabled() { this.contextRunner.withUserConfiguration(TestConfiguration7.class, EnableJmsConfiguration.class) - .withPropertyValues("spring.jms.listener.sessionTransacted=true") + .withPropertyValues("spring.jms.listener.session.transacted=true") .run((context) -> { DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); assertThat(container.isSessionTransacted()).isTrue(); @@ -216,7 +216,7 @@ class JmsAutoConfigurationTests { @Test void testDefaultContainerFactoryNoTransactionManagerAndSessionTransactedDisabled() { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) - .withPropertyValues("spring.jms.listener.sessionTransacted=false") + .withPropertyValues("spring.jms.listener.session.transacted=false") .run((context) -> { DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory"); assertThat(container.isSessionTransacted()).isFalse();