一起學習ActiveMQ (二) spring boot與ActiveMQ結合

一起學習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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章