Commit 7d5cc3da authored by Andy Wilkinson's avatar Andy Wilkinson

Stop ActiveMQ pooled connection factory when context is closed

Previously, ActiveMQ's pooled connection factory was not closed as
part of the application context being closed. This would leave
non-daemon threads running which could cause shutdown to hang unless
the JVM itself was shutting down (in which case a shutdown hook would
stop the pool).

This commit configures each pooled connection factory bean with a
custom destroy method so that the pool is stopped as part of the
application context being closed. To allow the destroy method to only
be declared when the connection factory is pooled, the bean method
has been split into two; one for pooled and one for non-pooled. This
is a partial backport of the changes made in bedf2edf.

Closes gh-4748
parent 474ffa71
...@@ -16,18 +16,16 @@ ...@@ -16,18 +16,16 @@
package org.springframework.boot.autoconfigure.jms.activemq; package org.springframework.boot.autoconfigure.jms.activemq;
import java.lang.reflect.Method;
import javax.jms.ConnectionFactory; import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.pool.PooledConnectionFactory; import org.apache.activemq.pool.PooledConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/** /**
* Configuration for ActiveMQ {@link ConnectionFactory}. * Configuration for ActiveMQ {@link ConnectionFactory}.
...@@ -43,31 +41,24 @@ import org.springframework.util.ReflectionUtils; ...@@ -43,31 +41,24 @@ import org.springframework.util.ReflectionUtils;
class ActiveMQConnectionFactoryConfiguration { class ActiveMQConnectionFactoryConfiguration {
@Bean @Bean
public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties) { @ConditionalOnProperty(prefix = "spring.activemq", name = "pooled", havingValue = "false", matchIfMissing = true)
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory( public ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties) {
properties).createConnectionFactory(ActiveMQConnectionFactory.class); return new ActiveMQConnectionFactoryFactory(properties)
if (properties.isPooled()) { .createConnectionFactory(ActiveMQConnectionFactory.class);
PooledConnectionFactory pool = new PooledConnectionFactory();
Method setConnectionFactory = findConnectionFactorySetter();
Assert.state(setConnectionFactory != null,
"No supported " + "setConnectionFactory method was found");
ReflectionUtils.invokeMethod(setConnectionFactory, pool, connectionFactory);
return pool;
}
return connectionFactory;
} }
private Method findConnectionFactorySetter() { @ConditionalOnClass(PooledConnectionFactory.class)
Method setter = findConnectionFactorySetter(ConnectionFactory.class); static class PooledConnectionFactoryConfiguration {
if (setter == null) {
setter = findConnectionFactorySetter(Object.class); @Bean(destroyMethod = "stop")
@ConditionalOnProperty(prefix = "spring.activemq", name = "pooled", havingValue = "true", matchIfMissing = false)
public PooledConnectionFactory pooledJmsConnectionFactory(
ActiveMQProperties properties) {
return new PooledConnectionFactory(
new ActiveMQConnectionFactoryFactory(properties)
.createConnectionFactory(ActiveMQConnectionFactory.class));
} }
return setter;
}
private Method findConnectionFactorySetter(Class<?> param) {
return ReflectionUtils.findMethod(PooledConnectionFactory.class,
"setConnectionFactory", param);
} }
} }
...@@ -26,6 +26,7 @@ import org.apache.activemq.pool.PooledConnectionFactory; ...@@ -26,6 +26,7 @@ import org.apache.activemq.pool.PooledConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.jta.XAConnectionFactoryWrapper; import org.springframework.boot.jta.XAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -53,15 +54,25 @@ class ActiveMQXAConnectionFactoryConfiguration { ...@@ -53,15 +54,25 @@ class ActiveMQXAConnectionFactoryConfiguration {
} }
@Bean @Bean
public ConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties) { @ConditionalOnProperty(prefix = "spring.activemq", name = "pooled", havingValue = "false", matchIfMissing = true)
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory( public ActiveMQConnectionFactory nonXaJmsConnectionFactory(
properties).createConnectionFactory(ActiveMQConnectionFactory.class); ActiveMQProperties properties) {
if (properties.isPooled()) { return new ActiveMQConnectionFactoryFactory(properties)
PooledConnectionFactory pool = new PooledConnectionFactory(); .createConnectionFactory(ActiveMQConnectionFactory.class);
pool.setConnectionFactory(connectionFactory); }
return pool;
@ConditionalOnClass(PooledConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.activemq", name = "pooled", havingValue = "true", matchIfMissing = false)
static class PooledConnectionFactoryConfiguration {
@Bean(destroyMethod = "stop")
public PooledConnectionFactory pooledNonXaJmsConnectionFactory(
ActiveMQProperties properties) {
return new PooledConnectionFactory(
new ActiveMQConnectionFactoryFactory(properties)
.createConnectionFactory(ActiveMQConnectionFactory.class));
} }
return connectionFactory;
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.jms.activemq; package org.springframework.boot.autoconfigure.jms.activemq;
import javax.jms.ConnectionFactory; import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.pool.PooledConnectionFactory; import org.apache.activemq.pool.PooledConnectionFactory;
...@@ -29,6 +30,8 @@ import org.springframework.context.annotation.Bean; ...@@ -29,6 +30,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -62,11 +65,14 @@ public class ActiveMQAutoConfigurationTests { ...@@ -62,11 +65,14 @@ public class ActiveMQAutoConfigurationTests {
} }
@Test @Test
public void pooledConnectionFactoryConfiguration() { public void pooledConnectionFactoryConfiguration() throws JMSException {
load(EmptyConfiguration.class, "spring.activemq.pooled:true"); load(EmptyConfiguration.class, "spring.activemq.pooled:true");
ConnectionFactory connectionFactory = this.context ConnectionFactory connectionFactory = this.context
.getBean(ConnectionFactory.class); .getBean(ConnectionFactory.class);
assertThat(connectionFactory, instanceOf(PooledConnectionFactory.class)); assertThat(connectionFactory, instanceOf(PooledConnectionFactory.class));
this.context.close();
assertThat(((PooledConnectionFactory) connectionFactory).createConnection(),
is(nullValue()));
} }
private void load(Class<?> config, String... environment) { private void load(Class<?> config, String... environment) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment