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 414ead5b7a..ccb1f730b0 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 @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.jms; +import java.time.Duration; + import javax.jms.ConnectionFactory; import org.springframework.jms.config.DefaultJmsListenerContainerFactory; @@ -107,6 +109,10 @@ public final class DefaultJmsListenerContainerFactoryConfigurer { if (concurrency != null) { factory.setConcurrency(concurrency); } + Duration receiveTimeout = listener.getReceiveTimeout(); + if (receiveTimeout != null) { + factory.setReceiveTimeout(receiveTimeout.toMillis()); + } } } 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 63d15ac1ca..aafab7c8fd 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 @@ -155,6 +155,13 @@ public class JmsProperties { */ private Integer maxConcurrency; + /** + * Timeout to use for receive calls. Use -1 for a no-wait receive or 0 for no + * timeout at all. The latter is only feasible if not running within a transaction + * manager and is generally discouraged since it prevents clean shutdown. + */ + private Duration receiveTimeout = Duration.ofSeconds(1); + public boolean isAutoStartup() { return this.autoStartup; } @@ -195,6 +202,14 @@ public class JmsProperties { : String.valueOf(this.concurrency)); } + public Duration getReceiveTimeout() { + return this.receiveTimeout; + } + + public void setReceiveTimeout(Duration receiveTimeout) { + this.receiveTimeout = receiveTimeout; + } + } public static class Template { 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 af7352db90..4989e3ce0f 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 @@ -147,7 +147,7 @@ class JmsAutoConfigurationTests { this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) .withPropertyValues("spring.jms.listener.autoStartup=false", "spring.jms.listener.acknowledgeMode=client", "spring.jms.listener.concurrency=2", - "spring.jms.listener.maxConcurrency=10") + "spring.jms.listener.receiveTimeout=2s", "spring.jms.listener.maxConcurrency=10") .run(this::testJmsListenerContainerFactoryWithCustomSettings); } @@ -157,6 +157,18 @@ class JmsAutoConfigurationTests { assertThat(container.getSessionAcknowledgeMode()).isEqualTo(Session.CLIENT_ACKNOWLEDGE); assertThat(container.getConcurrentConsumers()).isEqualTo(2); assertThat(container.getMaxConcurrentConsumers()).isEqualTo(10); + assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 2000L); + } + + @Test + void testJmsListenerContainerFactoryWithDefaultSettings() { + this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class) + .run(this::testJmsListenerContainerFactoryWithDefaultSettings); + } + + private void testJmsListenerContainerFactoryWithDefaultSettings(AssertableApplicationContext loaded) { + DefaultMessageListenerContainer container = getContainer(loaded, "jmsListenerContainerFactory"); + assertThat(container).hasFieldOrPropertyWithValue("receiveTimeout", 1000L); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java index ae4d4ac54b..2f339d7670 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsPropertiesTests.java @@ -20,6 +20,8 @@ import java.time.Duration; import org.junit.jupiter.api.Test; +import org.springframework.jms.listener.AbstractPollingMessageListenerContainer; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -78,4 +80,10 @@ class JmsPropertiesTests { assertThat(properties.getTemplate().determineQosEnabled()).isTrue(); } + @Test + void defaultReceiveTimeoutMatchesListenerContainersDefault() { + assertThat(new JmsProperties().getListener().getReceiveTimeout()) + .isEqualTo(Duration.ofMillis(AbstractPollingMessageListenerContainer.DEFAULT_RECEIVE_TIMEOUT)); + } + }