From 8365d535545b5683955a54eebaffbc3c7ffbcdda Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 7 Jun 2018 14:55:01 +0200 Subject: [PATCH] Add support for JMS session caching This commit adds support for CachingConnectionFactory for both Artemis and ActiveMQ. If connection pooling is not enabled explicitly, sessions, producers and consumers are cached. The factory can be further customized, including reverting to the raw ConnectionFactory, using the `spring.jms.*` namespace. Closes gh-12161 --- .../boot/autoconfigure/jms/JmsProperties.java | 64 +++++++++- .../activemq/ActiveMQAutoConfiguration.java | 5 +- ...ctiveMQConnectionFactoryConfiguration.java | 50 +++++++- .../jms/artemis/ArtemisAutoConfiguration.java | 5 +- ...ArtemisConnectionFactoryConfiguration.java | 46 ++++++- .../jms/JmsAutoConfigurationTests.java | 49 ++++---- .../ActiveMQAutoConfigurationTests.java | 53 +++++++- .../ArtemisAutoConfigurationTests.java | 119 ++++++++++++++---- .../appendix-application-properties.adoc | 4 + .../main/asciidoc/spring-boot-features.adoc | 22 +++- 10 files changed, 345 insertions(+), 72 deletions(-) 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 be584dc575..eff79905d5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -41,6 +41,8 @@ public class JmsProperties { */ private String jndiName; + private final Cache cache = new Cache(); + private final Listener listener = new Listener(); private final Template template = new Template(); @@ -61,6 +63,10 @@ public class JmsProperties { this.jndiName = jndiName; } + public Cache getCache() { + return this.cache; + } + public Listener getListener() { return this.listener; } @@ -69,6 +75,62 @@ public class JmsProperties { return this.template; } + public static class Cache { + + /** + * Whether to cache sessions. + */ + private boolean enabled = true; + + /** + * Whether to cache message consumers. + */ + private boolean consumers = false; + + /** + * Whether to cache message producers. + */ + private boolean producers = true; + + /** + * Size of the session cache (per JMS Session type). + */ + private int sessionCacheSize = 1; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isConsumers() { + return this.consumers; + } + + public void setConsumers(boolean consumers) { + this.consumers = consumers; + } + + public boolean isProducers() { + return this.producers; + } + + public void setProducers(boolean producers) { + this.producers = producers; + } + + public int getSessionCacheSize() { + return this.sessionCacheSize; + } + + public void setSessionCacheSize(int sessionCacheSize) { + this.sessionCacheSize = sessionCacheSize; + } + + } + public static class Listener { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java index b5462943cb..ed1140c83c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -45,7 +46,7 @@ import org.springframework.context.annotation.Import; @AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) @ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) @ConditionalOnMissingBean(ConnectionFactory.class) -@EnableConfigurationProperties(ActiveMQProperties.class) +@EnableConfigurationProperties({ ActiveMQProperties.class, JmsProperties.class }) @Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) public class ActiveMQAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java index e4a8a5a046..df32566cf4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java @@ -28,8 +28,10 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; /** * Configuration for ActiveMQ {@link ConnectionFactory}. @@ -45,13 +47,49 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnMissingBean(ConnectionFactory.class) class ActiveMQConnectionFactoryConfiguration { - @Bean + @Configuration @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true) - public ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider> factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.getIfAvailable()) - .createConnectionFactory(ActiveMQConnectionFactory.class); + static class SimpleConnectionFactoryConfiguration { + + private final JmsProperties jmsProperties; + + private final ActiveMQProperties properties; + + private final List connectionFactoryCustomizers; + + SimpleConnectionFactoryConfiguration(JmsProperties jmsProperties, + ActiveMQProperties properties, + ObjectProvider> connectionFactoryCustomizers) { + this.jmsProperties = jmsProperties; + this.properties = properties; + this.connectionFactoryCustomizers = connectionFactoryCustomizers + .getIfAvailable(); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true", matchIfMissing = true) + public CachingConnectionFactory cachingJmsConnectionFactory() { + JmsProperties.Cache cacheProperties = this.jmsProperties.getCache(); + CachingConnectionFactory connectionFactory = new CachingConnectionFactory( + createConnectionFactory()); + connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); + connectionFactory.setCacheProducers(cacheProperties.isProducers()); + connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); + return connectionFactory; + } + + @Bean + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") + public ActiveMQConnectionFactory jmsConnectionFactory() { + return createConnectionFactory(); + } + + private ActiveMQConnectionFactory createConnectionFactory() { + return new ActiveMQConnectionFactoryFactory(this.properties, + this.connectionFactoryCustomizers) + .createConnectionFactory(ActiveMQConnectionFactory.class); + } + } @Configuration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java index bff0d95c62..3f312f8e55 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -47,7 +48,7 @@ import org.springframework.context.annotation.Import; @AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) @ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) @ConditionalOnMissingBean(ConnectionFactory.class) -@EnableConfigurationProperties(ArtemisProperties.class) +@EnableConfigurationProperties({ ArtemisProperties.class, JmsProperties.class }) @Import({ ArtemisEmbeddedServerConfiguration.class, ArtemisXAConnectionFactoryConfiguration.class, ArtemisConnectionFactoryConfiguration.class }) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java index 51b9947f37..97bf6ea007 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java @@ -26,9 +26,11 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.activemq.PooledConnectionFactoryFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; /** * Configuration for Artemis {@link ConnectionFactory}. @@ -41,12 +43,46 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnMissingBean(ConnectionFactory.class) class ArtemisConnectionFactoryConfiguration { - @Bean + @Configuration @ConditionalOnProperty(prefix = "spring.artemis.pool", name = "enabled", havingValue = "false", matchIfMissing = true) - public ActiveMQConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, - ArtemisProperties properties) { - return new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQConnectionFactory.class); + static class SimpleConnectionFactoryConfiguration { + + private final JmsProperties jmsProperties; + + private final ArtemisProperties properties; + + private final ListableBeanFactory beanFactory; + + SimpleConnectionFactoryConfiguration(JmsProperties jmsProperties, + ArtemisProperties properties, ListableBeanFactory beanFactory) { + this.jmsProperties = jmsProperties; + this.properties = properties; + this.beanFactory = beanFactory; + } + + @Bean + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true", matchIfMissing = true) + public CachingConnectionFactory cachingJmsConnectionFactory() { + JmsProperties.Cache cacheProperties = this.jmsProperties.getCache(); + CachingConnectionFactory connectionFactory = new CachingConnectionFactory( + createConnectionFactory()); + connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); + connectionFactory.setCacheProducers(cacheProperties.isProducers()); + connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); + return connectionFactory; + } + + @Bean + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") + public ActiveMQConnectionFactory jmsConnectionFactory() { + return createConnectionFactory(); + } + + private ActiveMQConnectionFactory createConnectionFactory() { + return new ArtemisConnectionFactoryFactory(this.beanFactory, this.properties) + .createConnectionFactory(ActiveMQConnectionFactory.class); + } + } @Configuration 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 849a9e7c64..552afb6b9d 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 @@ -40,6 +40,7 @@ import org.springframework.jms.config.JmsListenerConfigUtils; import org.springframework.jms.config.JmsListenerContainerFactory; import org.springframework.jms.config.JmsListenerEndpoint; import org.springframework.jms.config.SimpleJmsListenerContainerFactory; +import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.core.JmsMessagingTemplate; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.listener.DefaultMessageListenerContainer; @@ -74,15 +75,17 @@ public class JmsAutoConfigurationTests { } private void testDefaultJmsConfiguration(AssertableApplicationContext loaded) { - ActiveMQConnectionFactory factory = loaded - .getBean(ActiveMQConnectionFactory.class); + assertThat(loaded).hasSingleBean(ConnectionFactory.class); + assertThat(loaded).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory factory = loaded.getBean(CachingConnectionFactory.class); + assertThat(factory.getTargetConnectionFactory()) + .isInstanceOf(ActiveMQConnectionFactory.class); JmsTemplate jmsTemplate = loaded.getBean(JmsTemplate.class); JmsMessagingTemplate messagingTemplate = loaded .getBean(JmsMessagingTemplate.class); assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); assertThat(messagingTemplate.getJmsTemplate()).isEqualTo(jmsTemplate); - assertThat(((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory()) - .getBrokerURL()).isEqualTo(ACTIVEMQ_EMBEDDED_URL); + assertThat(getBrokerUrl(factory)).isEqualTo(ACTIVEMQ_EMBEDDED_URL); assertThat(loaded.containsBean("jmsListenerContainerFactory")).isTrue(); } @@ -313,9 +316,10 @@ public class JmsAutoConfigurationTests { public void testPubSubDomainOverride() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.jms.pubSubDomain:false").run((context) -> { + assertThat(context).hasSingleBean(JmsTemplate.class); + assertThat(context).hasSingleBean(ConnectionFactory.class); JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); + ConnectionFactory factory = context.getBean(ConnectionFactory.class); assertThat(jmsTemplate).isNotNull(); assertThat(jmsTemplate.isPubSubDomain()).isFalse(); assertThat(factory).isNotNull() @@ -327,15 +331,13 @@ public class JmsAutoConfigurationTests { public void testActiveMQOverriddenStandalone() { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.activemq.inMemory:false").run((context) -> { + assertThat(context).hasSingleBean(JmsTemplate.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertThat(jmsTemplate).isNotNull(); - assertThat(factory).isNotNull() - .isEqualTo(jmsTemplate.getConnectionFactory()); - assertThat(((ActiveMQConnectionFactory) jmsTemplate - .getConnectionFactory()).getBrokerURL()) - .isEqualTo(ACTIVEMQ_NETWORK_URL); + ConnectionFactory factory = context.getBean(ConnectionFactory.class); + assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); + assertThat(getBrokerUrl((CachingConnectionFactory) factory)) + .isEqualTo(ACTIVEMQ_NETWORK_URL); }); } @@ -344,18 +346,23 @@ public class JmsAutoConfigurationTests { this.contextRunner.withUserConfiguration(TestConfiguration.class) .withPropertyValues("spring.activemq.brokerUrl:tcp://remote-host:10000") .run((context) -> { + assertThat(context).hasSingleBean(JmsTemplate.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertThat(jmsTemplate).isNotNull(); - assertThat(factory).isNotNull(); + ConnectionFactory factory = context.getBean(ConnectionFactory.class); assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); - assertThat(((ActiveMQConnectionFactory) jmsTemplate - .getConnectionFactory()).getBrokerURL()) - .isEqualTo("tcp://remote-host:10000"); + assertThat(getBrokerUrl((CachingConnectionFactory) factory)) + .isEqualTo("tcp://remote-host:10000"); }); } + private String getBrokerUrl(CachingConnectionFactory connectionFactory) { + assertThat(connectionFactory.getTargetConnectionFactory()) + .isInstanceOf(ActiveMQConnectionFactory.class); + return ((ActiveMQConnectionFactory) connectionFactory + .getTargetConnectionFactory()).getBrokerURL(); + } + @Test public void testActiveMQOverriddenPool() { this.contextRunner.withUserConfiguration(TestConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java index a1337dc94d..c65009f408 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -27,6 +27,8 @@ import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -50,10 +52,13 @@ public class ActiveMQAutoConfigurationTests { public void brokerIsEmbeddedByDefault() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class) .run((context) -> { - assertThat(context).getBean(ConnectionFactory.class) + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory cachingConnectionFactory = context + .getBean(CachingConnectionFactory.class); + assertThat(cachingConnectionFactory.getTargetConnectionFactory()) .isInstanceOf(ActiveMQConnectionFactory.class); - assertThat(context.getBean(ActiveMQConnectionFactory.class) - .getBrokerURL()) + assertThat(((ActiveMQConnectionFactory) cachingConnectionFactory + .getTargetConnectionFactory()).getBrokerURL()) .isEqualTo("vm://localhost?broker.persistent=false"); }); } @@ -68,10 +73,46 @@ public class ActiveMQAutoConfigurationTests { } @Test - public void defaultConnectionFactoryIsApplied() { + public void connectionFactoryIsCachedByDefault() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class) - .withPropertyValues("spring.activemq.pool.enabled=false") .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory connectionFactory = context + .getBean(CachingConnectionFactory.class); + assertThat(connectionFactory.getTargetConnectionFactory()) + .isInstanceOf(ActiveMQConnectionFactory.class); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheConsumers")).isEqualTo(false); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheProducers")).isEqualTo(true); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(1); + }); + } + + @Test + public void connectionFactoryCachingCanBeCustomized() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.consumers=true", + "spring.jms.cache.producers=false", + "spring.jms.cache.session-cache-size=10") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory connectionFactory = context + .getBean(CachingConnectionFactory.class); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheConsumers")).isEqualTo(true); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheProducers")).isEqualTo(false); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(10); + }); + } + + @Test + public void connectionFactoryCachingCanBeDisabled() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.enabled=false").run((context) -> { assertThat(context.getBeansOfType(ActiveMQConnectionFactory.class)) .hasSize(1); ActiveMQConnectionFactory connectionFactory = context @@ -99,7 +140,7 @@ public class ActiveMQAutoConfigurationTests { @Test public void customConnectionFactoryIsApplied() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class) - .withPropertyValues("spring.activemq.pool.enabled=false", + .withPropertyValues("spring.jms.cache.enabled=false", "spring.activemq.brokerUrl=vm://localhost?useJmx=false&broker.persistent=false", "spring.activemq.user=foo", "spring.activemq.password=bar", "spring.activemq.closeTimeout=500", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java index 25b015aee4..df581ab612 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java @@ -48,10 +48,12 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.SessionCallback; import org.springframework.jms.support.destination.DestinationResolver; import org.springframework.jms.support.destination.DynamicDestinationResolver; +import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -70,17 +72,69 @@ public class ArtemisAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ArtemisAutoConfiguration.class, JmsAutoConfiguration.class)); + @Test + public void connectionFactoryIsCachedByDefault() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory connectionFactory = context + .getBean(CachingConnectionFactory.class); + assertThat(connectionFactory.getTargetConnectionFactory()) + .isInstanceOf(ActiveMQConnectionFactory.class); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheConsumers")).isEqualTo(false); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheProducers")).isEqualTo(true); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(1); + }); + } + + @Test + public void connectionFactoryCachingCanBeCustomized() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.consumers=true", + "spring.jms.cache.producers=false", + "spring.jms.cache.session-cache-size=10") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + CachingConnectionFactory connectionFactory = context + .getBean(CachingConnectionFactory.class); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheConsumers")).isEqualTo(true); + assertThat(ReflectionTestUtils.getField(connectionFactory, + "cacheProducers")).isEqualTo(false); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(10); + }); + } + + @Test + public void connectionFactoryCachingCanBeDisabled() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.enabled=false").run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context).doesNotHaveBean(CachingConnectionFactory.class); + assertThat(context.getBean(ConnectionFactory.class)) + .isInstanceOf(ActiveMQConnectionFactory.class); + }); + } + @Test public void nativeConnectionFactory() { this.contextRunner.withUserConfiguration(EmptyConfiguration.class) .withPropertyValues("spring.artemis.mode:native").run((context) -> { JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); - assertNettyConnectionFactory(factory, "localhost", 61616); - assertThat(factory.getUser()).isNull(); - assertThat(factory.getPassword()).isNull(); + ConnectionFactory connectionFactory = context + .getBean(ConnectionFactory.class); + assertThat(connectionFactory) + .isEqualTo(jmsTemplate.getConnectionFactory()); + ActiveMQConnectionFactory activeMQConnectionFactory = getActiveMQConnectionFactory( + connectionFactory); + assertNettyConnectionFactory(activeMQConnectionFactory, "localhost", + 61616); + assertThat(activeMQConnectionFactory.getUser()).isNull(); + assertThat(activeMQConnectionFactory.getPassword()).isNull(); }); } @@ -90,9 +144,10 @@ public class ArtemisAutoConfigurationTests { .withPropertyValues("spring.artemis.mode:native", "spring.artemis.host:192.168.1.144", "spring.artemis.port:9876") .run((context) -> { - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertNettyConnectionFactory(factory, "192.168.1.144", 9876); + assertNettyConnectionFactory( + getActiveMQConnectionFactory( + context.getBean(ConnectionFactory.class)), + "192.168.1.144", 9876); }); } @@ -103,12 +158,17 @@ public class ArtemisAutoConfigurationTests { "spring.artemis.user:user", "spring.artemis.password:secret") .run((context) -> { JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertThat(factory).isEqualTo(jmsTemplate.getConnectionFactory()); - assertNettyConnectionFactory(factory, "localhost", 61616); - assertThat(factory.getUser()).isEqualTo("user"); - assertThat(factory.getPassword()).isEqualTo("secret"); + ConnectionFactory connectionFactory = context + .getBean(ConnectionFactory.class); + assertThat(connectionFactory) + .isEqualTo(jmsTemplate.getConnectionFactory()); + ActiveMQConnectionFactory activeMQConnectionFactory = getActiveMQConnectionFactory( + connectionFactory); + assertNettyConnectionFactory(activeMQConnectionFactory, "localhost", + 61616); + assertThat(activeMQConnectionFactory.getUser()).isEqualTo("user"); + assertThat(activeMQConnectionFactory.getPassword()) + .isEqualTo("secret"); }); } @@ -125,9 +185,8 @@ public class ArtemisAutoConfigurationTests { org.apache.activemq.artemis.core.config.Configuration.class); assertThat(configuration.isPersistenceEnabled()).isFalse(); assertThat(configuration.isSecurityEnabled()).isFalse(); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertInVmConnectionFactory(factory); + assertInVmConnectionFactory(getActiveMQConnectionFactory( + context.getBean(ConnectionFactory.class))); }); } @@ -142,9 +201,8 @@ public class ArtemisAutoConfigurationTests { org.apache.activemq.artemis.core.config.Configuration.class); assertThat(configuration.isPersistenceEnabled()).isFalse(); assertThat(configuration.isSecurityEnabled()).isFalse(); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertInVmConnectionFactory(factory); + assertInVmConnectionFactory(getActiveMQConnectionFactory( + context.getBean(ConnectionFactory.class))); }); } @@ -155,9 +213,10 @@ public class ArtemisAutoConfigurationTests { .withPropertyValues("spring.artemis.embedded.enabled:false") .run((context) -> { assertThat(context).doesNotHaveBean(EmbeddedJMS.class); - ActiveMQConnectionFactory factory = context - .getBean(ActiveMQConnectionFactory.class); - assertNettyConnectionFactory(factory, "localhost", 61616); + assertNettyConnectionFactory( + getActiveMQConnectionFactory( + context.getBean(ConnectionFactory.class)), + "localhost", 61616); }); } @@ -169,9 +228,8 @@ public class ArtemisAutoConfigurationTests { "spring.artemis.embedded.enabled:false") .run((context) -> { assertThat(context.getBeansOfType(EmbeddedJMS.class)).isEmpty(); - ActiveMQConnectionFactory connectionFactory = context - .getBean(ActiveMQConnectionFactory.class); - assertInVmConnectionFactory(connectionFactory); + assertInVmConnectionFactory(getActiveMQConnectionFactory( + context.getBean(ConnectionFactory.class))); }); } @@ -374,6 +432,13 @@ public class ArtemisAutoConfigurationTests { }); } + private ActiveMQConnectionFactory getActiveMQConnectionFactory( + ConnectionFactory connectionFactory) { + assertThat(connectionFactory).isInstanceOf(CachingConnectionFactory.class); + return (ActiveMQConnectionFactory) ((CachingConnectionFactory) connectionFactory) + .getTargetConnectionFactory(); + } + private TransportConfiguration assertInVmConnectionFactory( ActiveMQConnectionFactory connectionFactory) { TransportConfiguration transportConfig = getSingleTransportConfiguration( diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 8d27a40996..b51a13af0b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -995,6 +995,10 @@ content into your application. Rather, pick only the properties that you need. spring.integration.jdbc.schema=classpath:org/springframework/integration/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema. # JMS ({sc-spring-boot-autoconfigure}/jms/JmsProperties.{sc-ext}[JmsProperties]) + spring.jms.cache.consumers=false # Whether to cache message consumers. + spring.jms.cache.enabled=true # Whether to cache sessions. + spring.jms.cache.producers=true # Whether to cache message producers. + spring.jms.cache.session-cache-size=1 # Size of the session cache (per JMS Session type). spring.jms.jndi-name= # Connection factory JNDI name. When set, takes precedence to others connection factory auto-configurations. spring.jms.listener.acknowledge-mode= # Acknowledge mode of the container. By default, the listener is transacted with automatic acknowledgment. spring.jms.listener.auto-startup=true # Start the container automatically on startup. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 57b690451a..f67b0b7fef 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5065,7 +5065,16 @@ ActiveMQ configuration is controlled by external configuration properties in spring.activemq.password=secret ---- -You can also pool JMS resources by adding a dependency to +By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with +sensible settings that you can control by external configuration properties in +`+spring.jms.*+`: + +[source,properties,indent=0] +---- + spring.jms.cache.session-cache-size=5 +---- + +If you'd rather use native pooling, you can do so by adding a dependency to `org.apache.activemq:activemq-jms-pool` and configuring the `PooledConnectionFactory` accordingly, as shown in the following example: @@ -5122,7 +5131,16 @@ list to create them with the default options, or you can define bean(s) of type `org.apache.activemq.artemis.jms.server.config.TopicConfiguration`, for advanced queue and topic configurations, respectively. -You can also pool JMS resources by adding a dependency to +By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with +sensible settings that you can control by external configuration properties in +`+spring.jms.*+`: + +[source,properties,indent=0] +---- + spring.jms.cache.session-cache-size=5 +---- + +If you'd rather use native pooling, you can do so by adding a dependency to `org.apache.activemq:activemq-jms-pool` and configuring the `PooledConnectionFactory` accordingly, as shown in the following example: