消息中间件入门「二」:在spring中使用activemq
在普通的java项目使用activemq一般需要经过:1.获得连接工厂;2.创建连接;2.启动连接;3.获取会话(session);4.绑定连接地址(detination)5.获得消费者/生产者;6.生产/消费消息。
而在spring中使用activemq则可以将上述一系列的步骤写入spring的xml文件中,拓展性和复用性更好
项目结构
【非常简单有木有】
依赖jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-spring</artifactId>
<version>5.15.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--注入自定的消费者监听器bean和生产者,便于在app中测试-->
<context:component-scan base-package="queue"/>
<!--activeMq的连接工厂配置-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="trustAllPackages" value="true"/>
</bean>
<!--整合到spring的连接工厂中-->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
<property name="sessionCacheSize" value="100"/>
</bean>
<!--生产者发送消息的bean,相当于之前生产者绑定destination和send方法都集成在这个bean里面-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
</bean>
<!--队列-->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="queue"/>
</bean>
<!--主题-->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic"/>
</bean>
<!--现在监听器由spring托管,这部分相当于之前消费者的监听器部分代码-->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="queueDestination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
</beans>
说明:
1.trustAllPackages
设置为true表示信任所有,可以为所欲为的使用objectMessage传输消息。
2.CachingConnectionFactory
不一定非要这个连接工厂,在org.springframework.jms.connection
下有很多连接工厂,具体看官方api。
3.jmsTemplate
相当于整合了生产者建立连接的过程,在代码中发送Message,只需从IOC中取出这个bean,将发送的destination
和Message
传入send方法中即可。
4.注入了两个destination
的bean,一个是队列模式,一个是主题模式方便生产者和消费者绑定
5.由于消费者只需要确定接收消息的细节,即消息处理器[messageListener
],因此需要在代码中书写接收到消息后的细节,并将此bean注入到IOC中,而整个监听器是由spring托管的,因此放在了org.springframework.jms.listener.DefaultMessageListenerContainer
中,这了类里面实现了创建一个消费者具体处理逻辑,只需要将连接工厂[connectionFactory
],监听地址[queueDestination/topicDestination
],和消息处理器[messageListener
]传入即可。其中监听地址根据队列/主题模式传入对应的Destination。
消费者处理器代码:
package queue;
import org.springframework.stereotype.Component;
import javax.jms.*;
@Component("messageListener")
public class QueueConstumer implements MessageListener {
public void onMessage(Message message) {
ObjectMessage Message= (ObjectMessage) message;
try {
System.out.println("收到消息:"+Message.getObject());
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里的
"messageListener"
与xml配置文件中的jmsContainer
bean的messageListener
属性value是对应的,并且生产者传入的是什么Message类型消费取出时也必须要用与之对应的类型
生产者代码:
package queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
import pojo.User;
import javax.annotation.Resource;
import javax.jms.*;
@Component
public class QueueProducer {
@Autowired
JmsTemplate jmsTemplate;
@Resource(name = "queueDestination")
Destination destination;
public void sendMessage(final User msg){
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
final ObjectMessage objectMessage = session.createObjectMessage(msg);
return objectMessage;
}
});
System.out.println("发送消息:"+msg);
}
}
解释:
JmsTemplate
是前面spring配置文件中注入进去的,用于生产者发送Message;而注解@Resource(name = "queueDestination")
后面带参数是因为在配置文件中定义的destination都属于jms.Destination的实现。完全可以写成ActiveMQQueue/ActiveMQTopic即可不用带@resource
中的参数;发送消息指定调用JmsTemplate
的send方法将Destination和Message传入即可。若是切换模式[topic/queue],则只需要对应传入不同的destination即可。
主程序APP代码:
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;
import queue.QueueProducer;
import java.util.UUID;
public class App {
public static void main(String[] args) throws InterruptedException {
ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("activeMq-config.xml");
QueueProducer producer=applicationContext.getBean(QueueProducer.class);
User user=new User();
for (int i = 0; i < 100; i++) {
user.setAge(i);
user.setName((i%2==0? "Mr":"Mrs")+String.valueOf((char)(int)(Math.random()*26+'A'+1))+String.valueOf((char)(int)(Math.random()*26+'a')));
user.setSex(i%2==0? "男":"女");
user.setSalary((int)(Math.random()*10000));
user.setNumber(UUID.randomUUID().toString());
producer.sendMessage(user);
Thread.sleep(50);
}
applicationContext.close();
}
}
输出结果:
收到消息:User{name='MrNk', age=0, sex='男', number='b8fede00-2385-4b36-b70e-0b98dad260e2', salary=655}
发送消息:User{name='MrNk', age=0, sex='男', number='b8fede00-2385-4b36-b70e-0b98dad260e2', salary=655}
收到消息:User{name='MrsJm', age=1, sex='女', number='b3036d44-c7b0-4dbb-9ec2-de9b78e043d4', salary=5531}
发送消息:User{name='MrsJm', age=1, sex='女', number='b3036d44-c7b0-4dbb-9ec2-de9b78e043d4', salary=5531}
收到消息:User{name='MrXs', age=2, sex='男', number='68a2444a-8992-49b0-a9bd-cf00fcfb8763', salary=4556}
发送消息:User{name='MrXs', age=2, sex='男', number='68a2444a-8992-49b0-a9bd-cf00fcfb8763', salary=4556}
收到消息:User{name='MrsGf', age=3, sex='女', number='be4f0533-6721-49df-8174-e5cff5b1e24e', salary=2123}
发送消息:User{name='MrsGf', age=3, sex='女', number='be4f0533-6721-49df-8174-e5cff5b1e24e', salary=2123}
收到消息:User{name='MrBj', age=4, sex='男', number='958cdfa3-b97f-439a-b51c-30855b26d506', salary=5288}
可以看出由于都在一个项目中,消费者和生产者同时运行,因此一旦生产一条消息,立即被消费者取出。
主题/队列模式的切换:
由于spring简化了代码,因此切换模式也只需要修改两个地方即可:
- 队列模式
先修改配置中消费者的监听器配置:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="queueDestination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
只需要将destination
的ref指向队列下的地址:queueDestination
。
再修改生产者的
@resource
注解的name
值为队列下的:queueDestination
@Resource(name = "queueDestination")
- 主题模式
主题模式也一样,将生产者和消费者的detionation对应到:
topicDestination
即可:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="topicDestination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
修改@resource注解的参数为:topicDestination
@Resource(name = "topicDestination")