之前的連接工廠中,JmsTemlate每次發送消息時都會重新創建connection、session、productor等對象,從而導致性能的下降,是否有類似pool的連接池協助我們提高性能呢?答案必然是肯定的。
本章概要
1、緩衝連接工廠對比選擇;
2、源碼分析;
3、CachingConnectionFactory連接工廠的實現;
4、探索:ActiveMQXAConnectionFactory;
緩衝連接工廠對比選擇
1、ActiveMQ自身即有相關的實現PooledConnectionFactory,其只會緩存connection,session和productor,不會緩存consumer,故其適用於生產者。
2、而spring基於JMS標準進行了連接池的實現,故其不僅僅適用於ActiveMQ,並且還提供了對consumer的緩存實現。Spring定義了CachingConnectionFactory和SingleConnectionFactory,其均有緩存功能,但考慮到CachingConnectionFactory類擴展自SingleConnectionFactory,其有了更多的實現。
小結:故本章將通過CachingConnectionFactory實現帶有緩衝池的連接工廠。
源碼分析
1、從ActiveMQAutoConfiguration入手
再看ActiveMQConnectionFactoryConfiguration
以及ActiveMQXAConnectionFactoryConfiguration
其中均有條件註解@ConditionalOnMissingBean({ ConnectionFactory.class }),而此時我們正需要註冊自定義CachingConnectionFactory,故一旦我們註冊了系統默認的裝配的ConnectionFactory失效。
2、先了解下即將要註冊的CachingConnectionFactory,
又或者
均需要設置其真正生成連接的ConnectionFactory,考慮到1中默認的裝載已經失效,我們需要自己實現ConnectionFactory的註冊。
3、在ActiveMQConnectionFactoryConfiguration中可以找到
創建我們的ConnectionFactory,考慮到1.1中的描述其註解配置不會生效,故準備直接複製一個一樣的ActiveMQConnectionFactoryConfiguration來定義實現,但確發現 ActiveMQConnectionFactoryFactory類是沒有公開的,無法被我自定義的包路徑所識別應用,故只能從ActiveMQConnectionFactoryFactory自定義實現開始了。
4、當然我們也可以很簡單的直接在定義CachingConnectionFactory時直接new一個ActiveMQConnectionFactory實例。以上2、3的分析主要是結合springboot實現機制同步實現,便於更好的理解。
CachingConnectionFactory連接工廠的實現
1、創建ActiveMQConnectionFactory的工廠類,主要負責創建連接:
package com.shf.activemq.config;
import java.lang.reflect.InvocationTargetException;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* 創建ActiveMQConnectionFactory的工廠類
* @author song
*
*/
public class ActiveMQConnectionFactoryFactory {
private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
private final ActiveMQProperties properties;
ActiveMQConnectionFactoryFactory(ActiveMQProperties properties) {
Assert.notNull(properties, "Properties must not be null");
this.properties = properties;
}
public <T extends ActiveMQConnectionFactory> T createConnectionFactory(Class<T> factoryClass) {
try {
return doCreateConnectionFactory(factoryClass);
} catch (Exception ex) {
throw new IllegalStateException("Unable to create ActiveMQConnectionFactory", ex);
}
}
private <T extends ActiveMQConnectionFactory> T doCreateConnectionFactory(Class<T> factoryClass) throws Exception {
ActiveMQConnectionFactory factory = createConnectionFactoryInstance(factoryClass);
ActiveMQProperties.Packages packages = this.properties.getPackages();
if (packages.getTrustAll() != null) {
factory.setTrustAllPackages(packages.getTrustAll().booleanValue());
}
if (!(packages.getTrusted().isEmpty())) {
factory.setTrustedPackages(packages.getTrusted());
}
return (T) factory;
}
private <T extends ActiveMQConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass)
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
String brokerUrl = determineBrokerUrl();
String user = this.properties.getUser();
String password = this.properties.getPassword();
if ((StringUtils.hasLength(user)) && (StringUtils.hasLength(password))) {
return (T) ((ActiveMQConnectionFactory) factoryClass
.getConstructor(new Class[] { String.class, String.class, String.class })
.newInstance(new Object[] { user, password, brokerUrl }));
}
return (T) ((ActiveMQConnectionFactory) factoryClass.getConstructor(new Class[] { String.class })
.newInstance(new Object[] { brokerUrl }));
}
String determineBrokerUrl() {
if (this.properties.getBrokerUrl() != null) {
return this.properties.getBrokerUrl();
}
if (this.properties.isInMemory()) {
return "vm://localhost?broker.persistent=false";
}
return "tcp://localhost:61616";
}
}
2、配置註冊連接工廠:
package com.shf.activemq.config;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.jta.XAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置註冊連接工廠
* @author song
*
*/
@Configuration
public class ActiveMQXAConnectionFactoryConfiguration {
/**
* 創建普通連接工廠
* @param properties
* @return
*/
@Bean(name = "jmsConnectionFactory")
public ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties) {
return new ActiveMQConnectionFactoryFactory(properties)
.createConnectionFactory(ActiveMQConnectionFactory.class);
}
}
3、註冊帶有session緩衝的連接工廠
package com.shf.activemq.config;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;
/**
* 定義帶有session緩衝的連接工廠
* @author song
*
*/
@Configuration
@EnableConfigurationProperties({ ActiveMQProperties.class })
public class ActiveMQConnectionFactoryConfiguration {
/**
* 注入MQ連接工廠
*/
@Autowired
@Qualifier(value="jmsConnectionFactory")
private ConnectionFactory connectionFactory;
/**
* 創建帶有緩衝session的連接工廠
* @return
*/
@Bean(name="cachingConnectionFactory")
@Primary
public CachingConnectionFactory getConnectionFactory(){
CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory();
//目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory
cachingConnectionFactory.setTargetConnectionFactory(connectionFactory);
//Session緩存數量,這裏屬性也可以直接在這裏配置
cachingConnectionFactory.setSessionCacheSize(10);
return cachingConnectionFactory;
}
}
4、優化JmsTemplate註冊,其將應用3中定義的CachingConnectionFactory :
package com.shf.activemq.config;
import javax.jms.DeliveryMode;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.destination.DestinationResolver;
/**
* 自定義JmsTemplate,支持事務
* @author song
*
*/
@Configuration
@DependsOn(value="cachingConnectionFactory")
public class JmsTemplateConfiguration {
private final ObjectProvider<DestinationResolver> destinationResolver;
private final ObjectProvider<MessageConverter> messageConverter;
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
public JmsTemplateConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
ObjectProvider<MessageConverter> messageConverter) {
this.destinationResolver = destinationResolver;
this.messageConverter = messageConverter;
}
/**
* 配置隊列生產者的JmsTemplate
* @param connectionFactory
* @return
*/
@Bean(name="jmsQueueTemplate")
@Primary
public JmsTemplate jmsQueueTemplate() {
//設置創建連接的工廠
// JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
//優化連接工廠,這裏應用緩存池 連接工廠就即可
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
//設置P2P隊列消息類型
jmsTemplate.setPubSubDomain(false);
DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
if (destinationResolver != null) {
jmsTemplate.setDestinationResolver(destinationResolver);
}
MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
if (messageConverter != null) {
jmsTemplate.setMessageConverter(messageConverter);
}
//deliveryMode, priority, timeToLive 的開關,要生效,必須配置爲true,默認false
jmsTemplate.setExplicitQosEnabled(true);
//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
//默認不開啓事務
System.out.println("默認是否開啓事務:"+jmsTemplate.isSessionTransacted());
//如果不啓用事務,則會導致XA事務失效;
//作爲生產者如果需要支持事務,則需要配置SessionTransacted爲true
jmsTemplate.setSessionTransacted(true);
return jmsTemplate;
}
/**
* 配置發佈訂閱生產者的JmsTemplate
* @param connectionFactory
* @return
*/
@Bean(name="jmsTopicTemplate")
public JmsTemplate jmsTopicTemplate() {
//設置創建連接的工廠
// JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
//優化連接工廠,這裏應用緩存池 連接工廠就即可
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
//設置發佈訂閱消息類型
jmsTemplate.setPubSubDomain(true);
DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();
if (destinationResolver != null) {
jmsTemplate.setDestinationResolver(destinationResolver);
}
MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();
if (messageConverter != null) {
jmsTemplate.setMessageConverter(messageConverter);
}
//deliveryMode, priority, timeToLive 的開關,要生效,必須配置爲true,默認false
jmsTemplate.setExplicitQosEnabled(true);
//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
//默認不開啓事務
System.out.println("默認是否開啓事務:"+jmsTemplate.isSessionTransacted());
//如果不啓用事務,則會導致XA事務失效;
//作爲生產者如果需要支持事務,則需要配置SessionTransacted爲true
jmsTemplate.setSessionTransacted(true);
return jmsTemplate;
}
}
5、通過之前章節中的單元測試代碼驗證, 不僅CachingConnectionFactory 連接工廠配置成功,其事務仍然生效。
探索
在源碼中我們看到了ActiveMQXAConnectionFactoryConfiguration中不僅有常規普通的連接工廠,還有ActiveMQXAConnectionFactory工廠註冊,其標註了
需要注入XAConnectionFactoryWrapper的實例,但其實源碼中並沒有註解配置XAConnectionFactoryWrapper的bean實例,如果需要應用ActiveMQXAConnectionFactory我們需要優先註冊XAConnectionFactoryWrapper,本案例採用AtomikosXAConnectionFactoryWrapper
。
1、註冊AtomikosXAConnectionFactoryWrapper 實例:
package com.shf.activemq.config;
import org.springframework.boot.jta.atomikos.AtomikosXAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置AtomikosXAConnectionFactoryWrapper的bean實例,否則無法創建ActiveMQXAConnectionFactory工廠
* @author song
*
*/
@Configuration
public class AtomikosXAConnectionFactoryWrapperConfiguration {
@Bean
public AtomikosXAConnectionFactoryWrapper atomikosXAConnectionFactoryWrapper(){
return new AtomikosXAConnectionFactoryWrapper();
}
}
2、調整上述的ActiveMQXAConnectionFactoryConfiguration,新增一個ActiveMQXAConnectionFactory的bean註冊:
package com.shf.activemq.config;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.jta.XAConnectionFactoryWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置註冊連接工廠
* @author song
*
*/
@Configuration
public class ActiveMQXAConnectionFactoryConfiguration {
/**
* 創建XA連接工廠
* @param properties
* @param wrapper
* @return
* @throws Exception
*/
@Bean(name = "xaJmsConnectionFactory")
public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, XAConnectionFactoryWrapper wrapper)
throws Exception {
ActiveMQXAConnectionFactory connectionFactory = (ActiveMQXAConnectionFactory) new ActiveMQConnectionFactoryFactory(
properties).createConnectionFactory(ActiveMQXAConnectionFactory.class);
return wrapper.wrapConnectionFactory(connectionFactory);
}
/**
* 創建普通連接工廠
* @param properties
* @return
*/
@Bean(name = "jmsConnectionFactory")
public ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties) {
return new ActiveMQConnectionFactoryFactory(properties)
.createConnectionFactory(ActiveMQConnectionFactory.class);
}
}
3、調整配置CachingConnectionFactory 中注入的ConnectionFactory 實例:
package com.shf.activemq.config;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jms.connection.CachingConnectionFactory;
/**
* 定義帶有session緩衝的連接工廠
* @author song
*
*/
@Configuration
@EnableConfigurationProperties({ ActiveMQProperties.class })
public class ActiveMQConnectionFactoryConfiguration {
/**
* 注入MQ連接工廠
*/
@Autowired
// @Qualifier(value="jmsConnectionFactory")//雖然不是ActiveMQXAConnectionFactory,但在JTA事務管理器的事務處理下能夠很好的實現分佈式事務
@Qualifier(value="xaJmsConnectionFactory")//在JTA事務管理器的事務處理下能夠很好的實現分佈式事務
private ConnectionFactory connectionFactory;
/**
* 創建帶有緩衝session的連接工廠
* @return
*/
@Bean(name="cachingConnectionFactory")
@Primary
public CachingConnectionFactory getConnectionFactory(){
CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory();
//目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory
cachingConnectionFactory.setTargetConnectionFactory(connectionFactory);
//Session緩存數量,這裏屬性也可以直接在這裏配置
cachingConnectionFactory.setSessionCacheSize(10);
return cachingConnectionFactory;
}
}
4、通過之前章節中的單元測試代碼驗證, 不僅CachingConnectionFactory 連接工廠配置成功,其事務仍然生效。
小結:不管注入使用的是ActiveMQXAConnectionFactory還是ActiveMQConnectionFactory,其應用內的分佈式事務均能生效。