一起學習ActiveMQ (二) spring boot與ActiveMQ結合
前面一章介紹了ActiveMQ 的安裝及相關api,但是通常情況下我們很少直接使用原生api進行開發,同時spring jms對ActiveMQ 進行了一系列的封裝,再加上spring boot的快捷開發,使得對ActiveMQ的使用更加簡單高效。下面一起來看下如何利用spring boot連接ActiveMQ。
1. 導入相關依賴
在pom裏面加入如下代碼
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
idea中我們可以點進該依賴裏面看下,這個依賴裏面加了什麼東西:
<!-- 引入spring jms -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.1.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- 引入activemq -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.8</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>geronimo-jms_1.1_spec</artifactId>
<groupId>org.apache.geronimo.specs</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入javax.jms-api-->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
<scope>compile</scope>
</dependency>
如上所示,spring-boot-starter-activemq主要是將sping jms和activemq的一些相關jar包進行引入。
2. 編寫生產者和消費者代碼
- 配置隊列
在發送消息之前我們先要配置好隊列
@Configuration
public class QueueConfiguration {
@Value("${test.queue.name}")
private String queueName;
@Bean
public Queue sampleQueue() {
return new ActiveMQQueue(queueName);
}
}
這裏配置一個名稱可配置的ActiveMQQueue
- 生產者
spring-jms對activemq提供了JmsMessagingTemplate類似jdbc的一些模版方法,便於向activemq發送和接受消息。
下面來看下生產者的代碼:
@Component
public class Producer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue sampleQueue;
@Scheduled(cron = "0/2 * * * * ?")
public void run() throws Exception {
this.jmsMessagingTemplate.convertAndSend(sampleQueue, "定時消息" + System.currentTimeMillis());
}
}
首先我們注入JmsMessagingTemplate ,然後注入剛剛配置的Queue,使用.jmsMessagingTemplate.convertAndSend發送消息到mq。JmsMessagingTemplate 定義了很多send方法,這個大家可以根據需要選擇使用哪一個方法,這裏我們用一個定時器定時推送消息。
- 消費者
消費者也可以使用JmsMessagingTemplate 的receive方法進行接收消息,但是該方法每次只能接收1條消息,通常的用法是使用jms的一個監聽器。
@Component
public class Consumer {
@JmsListener(destination = "${test.queue.name}")
public void receiveQueue(String text) {
System.out.println(text);
}
}
如上圖使用@JmsListener(destination = “${test.queue.name}”)標註該方法是一個接收消息的方法,這樣就不用自己循環調用jmsMessagingTemplate.receive方法。
- 配置及啓動
在配置文件中加入如下配置,關於activemq的配置還有很多這裏不一一列舉
spring.activemq.in-memory=false
spring.activemq.pool.enabled=false
spring.activemq.broker-url=failover:(tcp://localhost:61616)
spring.activemq.password=admin
spring.activemq.user=admin
spring.activemq.send-timeout=100ms
test.queue.name=sample.queue
啓動類
@SpringBootApplication
//啓用jms
@EnableJms
//啓用定時調度
@EnableScheduling
public class SampleActiveMQApplication {
public static void main(String[] args) {
SpringApplication.run(SampleActiveMQApplication.class, args);
}
}
注意一定要加上@EnableJms表示啓用jms,@EnableScheduling表示啓用定時器
- 測試
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.1.RELEASE)
2019-07-24 21:35:17.260 INFO 10104 --- [ main] com.foo.SampleActiveMQApplication : Starting SampleActiveMQApplication on DESKTOP-QSORQJP with PID 10104 (D:\idea-workspace\github\spring-boot-practice\spring-boot-pratice-activemq\target\classes started by LinYu in D:\idea-workspace\github\spring-boot-practice)
2019-07-24 21:35:17.263 INFO 10104 --- [ main] com.foo.SampleActiveMQApplication : No active profile set, falling back to default profiles: default
2019-07-24 21:35:18.261 INFO 10104 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2019-07-24 21:35:18.494 INFO 10104 --- [ActiveMQ Task-1] o.a.a.t.failover.FailoverTransport : Successfully connected to tcp://localhost:61616
2019-07-24 21:35:18.515 INFO 10104 --- [ main] com.foo.SampleActiveMQApplication : Started SampleActiveMQApplication in 1.499 seconds (JVM running for 2.205)
定時消息1563975320003
定時消息1563975322002
定時消息1563975324001
定時消息1563975326002
打印日誌如上,說明配置正確,到這裏我們就能正常的接收和發送消息了。
3. spring boot 自動注入ActiveMQ原理分析
上面我們看了簡單的demo,下面我們來更深入的學習一下,sping boot是如何自動注入的。
在maven lib欄找到spring-boot-autoconfigure包,找到jms/activemq目錄
如上圖所示兩個Configuration就是spring boot自動注入的祕密所在,其它的一些組件都是通過類似的方法實現的。
@Configuration
@AutoConfigureBefore(JmsAutoConfiguration.class)
@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class })
@ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class })
@ConditionalOnMissingBean(ConnectionFactory.class)
@EnableConfigurationProperties({ ActiveMQProperties.class, JmsProperties.class })
@Import({ ActiveMQXAConnectionFactoryConfiguration.class,
ActiveMQConnectionFactoryConfiguration.class })
public class ActiveMQAutoConfiguration {
}
ActiveMQAutoConfiguration 沒有實現任何內容,上面的一些註解主要是條件判斷是否能夠自動進行注入。接下來我們看下ActiveMQConnectionFactoryConfiguration 如何自動注入的
@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
class ActiveMQConnectionFactoryConfiguration {
@Configuration
@ConditionalOnClass(CachingConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true)
static class SimpleConnectionFactoryConfiguration {
private final JmsProperties jmsProperties;
private final ActiveMQProperties properties;
private final List<ActiveMQConnectionFactoryCustomizer> connectionFactoryCustomizers;
SimpleConnectionFactoryConfiguration(JmsProperties jmsProperties,
ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> connectionFactoryCustomizers) {
this.jmsProperties = jmsProperties;
this.properties = properties;
this.connectionFactoryCustomizers = connectionFactoryCustomizers
.orderedStream().collect(Collectors.toList());
}
@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
@ConditionalOnClass({ JmsPoolConnectionFactory.class, PooledObject.class })
static class PooledConnectionFactoryConfiguration {
@Bean(destroyMethod = "stop")
@ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true", matchIfMissing = false)
public JmsPoolConnectionFactory pooledJmsConnectionFactory(
ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
properties,
factoryCustomizers.orderedStream().collect(Collectors.toList()))
.createConnectionFactory(ActiveMQConnectionFactory.class);
return new JmsPoolConnectionFactoryFactory(properties.getPool())
.createPooledConnectionFactory(connectionFactory);
}
}
}
如果我們只是單純的使用spring-jms而不配置ActiveMQConnectionFactory ,JmsMessagingTemplate每次都會進行連接的打開和關閉,這樣對資源消耗非常的大,好在spring jms實現了連接池,對連接進行重用。
@ConditionalOnClass(CachingConnectionFactory.class)
表示需要類路徑下存在CachingConnectionFactory.class纔會生效
@ConditionalOnProperty(prefix = “spring.activemq.pool”, name = “enabled”, havingValue = “false”, matchIfMissing = true)
表示spring.activemq.pool.name的值必須是false纔會生效
如上所述我們之前的配置剛好滿足SimpleConnectionFactoryConfiguration的自動注入條件,可以自行debug。
接來下是通過配置文件配置JmsProperties,以spring.jms開頭的配置會注入到JmsProperties中,然後是connectionFactoryCustomizers自定義的連接池配置。
接下來是注入connectionFactory,如上代碼所示,該配置下有兩種connectionFactory:CachingConnectionFactory和原生的ActiveMQConnectionFactory注入的條件取決於
@ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
表示spring.jms.cache.enabled=true就自動注入,而ActiveMQConnectionFactory的條件則相反。cache的配置是在JmsProperties裏面,而org.springframework.boot.autoconfigure.jms.JmsProperties.Cache#enabled默認爲ture
所以系統就默認爲我們注入了CachingConnectionFactory。而CachingConnectionFactory會爲我們緩存連接。
當我們使用
spring.activemq.pool.enabled=true
時,我們就必須引入JmsPool相關的jar包,並且使用JmsPoolConnectionFactory,關於CachingConnectionFactory、JmsPoolConnectionFactory和ActiveMQConnectionFactory單獨再講,此處不做過多講述。
如上就是spring boot自動注入ActiveMQ的原理。
4. 相關配置優化
既然spring.activemq.pool.enabled默認爲true則該條配置可以刪除
spring.activemq.in-memory=false
spring.activemq.broker-url=failover:(tcp://localhost:61616)?jms.useAsyncSend=true
spring.activemq.password=admin
spring.activemq.user=admin
spring.activemq.send-timeout=100ms
#緩存消費者
spring.jms.cache.consumers=true
#設置緩存的大小
spring.jms.cache.session-cache-size=20
#設置消費者的併發數量
spring.jms.listener.concurrency=20
#設置消費者的最大併發數量
spring.jms.listener.max-concurrency=40
#設置要反序列化的可信包列表,用於對象消息的安全傳遞http://activemq.apache.org/objectmessage.html
spring.activemq.packages.trusted="com.foo"
消費者併發度也可以通過註解配置:1-20表示最小是1最大20個線程執行
@JmsListener(destination = “${test.queue.name}”, concurrency = “1-20”)
我們更改生產者和消費者的代碼,將生產者消息發生頻率變大,消費者消費速度降低
運行後日志如下:
可以看到一共有3個消費者線程。sping會根據當前消息的堆積量自動調整消費者的線程數。
OK,spring boot與ActiveMQ結合到這就講完了,還有很多的特性需要大家自己去學習啦,完整的代碼路徑:https://github.com/Json-Lin/spring-boot-practice/tree/master/spring-boot-pratice-activemq。