1、JMS
JMS是java的消息服务,即Java Message Service,分为消息生产者(Producer)与消息消费者(consumer),JMS具体实现依赖于消息中间件。
2、消息中间件
消息中间件(MOM:Message Orient middleware)。
消息中间件有很多的用途和优点:
1. 将数据从一个应用程序传送到另一个应用程序,或者从软件的一个模块传送到另外一个模块;
2. 负责建立网络通信的通道,进行数据的可靠传送。
3. 保证数据不重发,不丢失
4. 能够实现跨平台操作,能够为不同操作系统上的软件集成技工数据传送服务
- 消息的接受和转发的容器
3、ActiveMQ
下载ActiveMQ
官方网站:http://activemq.apache.org/
安装目录:
- bin存放的是脚本文件
- conf存放的是基本配置文件
- data存放的是日志文件
- docs存放的是说明文档
- examples存放的是简单的实例
- lib存放的是activemq所需jar包
- webapps用于存放项目的目录
ActiveMQ 启动
双击bin目录下的activemq.bat脚本文件或运行自己电脑版本下的activemq.bat,从图中可以看到activemq的存放地址,以及浏览器要访问的地址. 。
ActiveMQ 测试
ActiveMQ默认使用的TCP连接端口是61616, 通过查看该端口的信息可以测试ActiveMQ是否成功启动 netstat -an|find “61616”
C:\Documents and Settings\Administrator>netstat -an|find "61616"
TCP 0.0.0.0:61616 0.0.0.0:0 LISTENING
ActiveMQ 监测
ActiveMQ默认启动时,启动了内置的jetty服务器,提供一个用于监控ActiveMQ的admin应用。
admin:http://127.0.0.1:8161/admin/
用户名和密码都是admin
停止服务器,只需要按着Ctrl+Shift+C,之后输入y即可。
4、消息模型
点对点/一对一:
- 消息队列(Queue)
- 发送者(Sender)
- 接收者(Receiver)
- 每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
多对多/发布订阅模式
- 主题(Topic)
- 发布者(Publisher)
- 订阅者(Subscriber)
- 客户端将消息发送到主题。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者
消息的消费
在JMS中,消息的产生和消息是异步的。对于消费来说,JMS的消息者可以通过两种方式来消费消息。
- ○ 同步 :订阅者或接收者调用receive方法来接收消息,receive方法在能够接收到消息之前(或超时之前)将一直阻塞
- ○ 异步 :订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage方法。
5、 JMS编程涉及的主要对象
ConnectionFactory:创建Connection对象的工厂,分QueueConnectionFactory和TopicConnectionFactory两种。
Connection:Connection表示在客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection可以产生一个或多个Session。
Session:是我们操作消息的接口。可以通过session创建生产者、消费者、消息等。Session提供了事务的功能。当我们需要使用session发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。
Destination:消息发送目标或消息来源。对于消息生产者来说,其Destination是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,其Destination也是某个队列或主题(即消息来源)。
producer:由Session创建,并用于将消息发送到Destination。分两种类型:QueueSender和TopicPublisher。可以调用消息生产者的方法(send或publish方法)发送消息。
consumer:由Session创建,用于接收被发送到Destination的消息。分两种类型:QueueReceiver和TopicSubscriber。可分别通过session的createConsumer(Queue)或createSubscriber(Topic)来创建。当然,也可以session的creatDurableSubscriber方法来创建持久化的订阅者。
MessageListener:消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的onMessage方法。EJB中的MDB(Message-Driven Bean)就是一种MessageListener。
6、demo
发送消息
public static void main(String[] args) {
//实例化连接工厂
ConnectionFactory connectionFactory = new
ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);
try {
//通过连接工厂获取连接
Connection connection = connectionFactory.createConnection();
//启动连接
connection.start();
//创建session
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//创建一个名称为HelloWorld的消息队列
Destination destination = session.createQueue("HelloWorld");
//创建消息生产者
MessageProducer messageProducer = session.createProducer(destination);
//设置不持久化,此处学习,实际根据项目决定
messageProducer.setDeliveryMode(DeliveryMode. PERSISTENT);
//发送消息
sendMessage(session, messageProducer);
session.commit();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(connection != null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* 发送消息
* @param session
* @param messageProducer 消息生产者
* @throws Exception
*/
public static void sendMessage(Session session,MessageProducer messageProducer) throws Exception{
for (int i = 0; i < 10; i++) {
//创建一条文本消息
TextMessage message = session.createTextMessage("发送消息" +i);
System.out.println("发送消息: 发送消息" + i);
//通过消息生产者发出消息
messageProducer.send(message);
}
}
接受消息
public static void main(String[] args) {
//实例化连接工厂
ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);
try {
//通过连接工厂获取连接
Connection connection = connectionFactory.createConnection();
//启动连接
connection.start();
//创建session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建一个连接HelloWorld的消息队列
Destination destination = session.createQueue("HelloWorld");
//创建消息消费者
messageConsumer = session.createConsumer(destination);
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);
if(textMessage != null){
System.out.println("收到的消息:" + textMessage.getText());
}else {
break;
}
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
运行ActiveMQ服务器,能查看消息生产与消费情况
7、Spring和ActiveMQ整合
<!-- ActiveMQ 连接工厂 -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<!-- 如果连接网络:tcp://ip:61616;未连接网络:tcp://localhost:61616 以及用户名,密码-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="failover:tcp://127.0.0.1:61616" />
<!-- 消息传输监听器 处理网络及服务器异常 -->
<property name="transportListener">
<bean class="cn.mayongfa.activemq.ActiveMQTransportListener" />
</property>
</bean>
<!-- Spring Caching连接工厂 -->
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- 同上,同理 -->
<!-- <constructor-arg ref="amqConnectionFactory" /> -->
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="100" />
</bean>
<!-- Spring JmsTemplate 的消息生产者 start-->
<!-- 定义JmsTemplate的Queue类型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="connectionFactory" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定义JmsTemplate的Topic类型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="connectionFactory" />
<!-- pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="true" />
</bean>
<!--这个是队列目的地 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>default_queue,gold_queue</value>
</constructor-arg>
</bean>
<!-- 消息监听器 -->
<bean id="consumerMessageListener" class="cn.mayongfa.activemq.ConsumerMessageListener" />
<!-- 消息监听容器 -->
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="consumerMessageListener" />
<!-- 设置固定的线程数 -->
<property name="concurrentConsumers" value="2"></property>
<!-- 设置动态的线程数 -->
<property name="concurrency" value="2-5"></property>
</bean>
</beans>
消息生产类
/**
* ActiveMQ 消息生产类
*
* @author Mafly
*
*/
@Component
public class MessageSender {
private Logger log = Logger.getLogger(MessageSender.class);
@Autowired
private JmsTemplate jmsTemplate;
private String Queue = "default_queue";
private String GoldQueue = "gold_queue";
private Gson gson = new Gson();
/**
* 用户登录消息
*/
public void userLogin(long id, String username) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("userid", id);
map.put("username", username);
System.out.println("发送了一条消息。");
// 发送到金币队列
sendMessage(gson.toJson(map), 1);
}
/**
* 发送到消息队列
*
* @param messgae
* @param type
* 类型,0:默认队列 1:金币队列 ...
*/
public void sendMessage(final String messgae, int type) {
try {
String destination = this.Queue;
if (type == 1) {
destination = GoldQueue;
}
jmsTemplate.send(destination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage(messgae);
return textMessage;
}
});
} catch (Exception e) {
log.error("", e);
}
}
}
消息消费类
/**
* 消费者监听类
*
* @author Mafly
*/
@Component
public class ConsumerMessageListener implements MessageListener {
private Logger log = Logger.getLogger(ConsumerMessageListener.class);
@Override
public void onMessage(Message arg0) {
// 监听发送到消息队列的文本消息,作强制转换。
TextMessage textMessage = (TextMessage) arg0;
try {
System.out.println("接收到的消息内容是:" + textMessage.getText());
// TODO: 你喜欢的任何事情...
} catch (JMSException e) {
log.error("", e);
}
}
}
消息传输监听类
/**
* 消息传输监听
* @author Mafly
*
*/
public class ActiveMQTransportListener implements TransportListener {
private Logger log = Logger.getLogger(ActiveMQTransportListener.class);
/**
* 对消息传输命令进行监控
* @param command
*/
@Override
public void onCommand(Object o) {
}
/**
* 对监控到的异常进行触发
* @param error
*/
@Override
public void onException(IOException error) {
log.error("onException -> 消息服务器连接错误......", error);
}
/**
* 当failover时触发
*/
@Override
public void transportInterupted() {
log.warn("transportInterupted -> 消息服务器连接发生中断...");
//这里就可以状态进行标识了
}
/**
* 监控到failover恢复后进行触发
*/
@Override
public void transportResumed() {
log.info("transportResumed -> 消息服务器连接已恢复...");
//这里就可以进行状态标识了
}
}
8 ActiveMQ持久订阅设置
ActiveMQ支持两种传输模式:持久传输和非持久传输(persistent and non-persistent delivery),默认情况下使用的是持久传输。
持久传输和非持久传输最大的区别是:采用持久传输时,传输的消息会保存到磁盘中(messages are persisted to disk/database),即“存储转发”方式。先把消息存储到磁盘中,然后再将消息“转发”给订阅者。
采用非持久传输时,发送的消息不会存储到磁盘中。
采用持久传输时,当Borker宕机 恢复后,消息还在。采用非持久传输,Borker宕机重启后,消息丢失。比如,当生产者将消息投递给Broker后,Broker将该消息存储到磁盘中,在Broker将消息发送给Subscriber之前,Broker宕机了,如果采用持久传输,Broker重启后,从磁盘中读出消息再传递给Subscriber;如果采用非持久传输,这条消息就丢失了。
先设置消息的持久
//消息持久化
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
订阅者设置持久
public static void main(String[] args) {
Connection connection = null;
try {
ActiveMQConnectionFactory connectionFactxory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);
//创建连接
connection = connectionFactxory.createConnection();
//设置持久id
connection.setClientID("aa");
connection.start();
//获取session
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//创建消息主题
Topic topic = session.createTopic("msg_4_publish");
//创建普通消息消费者
// MessageConsumer consumer = session.createConsumer(topic);
//创建持久消息消费者
TopicSubscriber subscriber = session.createDurableSubscriber(topic, "aa");
while (true) {
// TextMessage message = (TextMessage)consumer.receive();
TextMessage message = (TextMessage)subscriber.receive();
if(message!=null){
System.out.println("subscriber_1 收到消息:"+message.getText());
}else {
break;
}
}
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
9 与spring整合的持久化
消息生产者配置文件
<!--第三方工厂 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://127.0.0.1:61616" />
<property name="userName" value="admin"></property>
<property name="password" value="admin"></property>
<property name="useAsyncSend" value="true" />
</bean>
<!-- ActiveMQ为我们提供了一个PooledConnectionFactory,通过往里面注入一个ActiveMQConnectionFactory
可以用来将Connection、Session和MessageProducer池化,这样可以大大的减少我们的资源消耗,要依赖于 activemq-pool包 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="connectionFactory" ref="targetConnectionFactory" />
<property name="maxConnections" value="100" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="pooledConnectionFactory" />
<!--消费者标示id -->
<property name="clientId" value="clientId_001" />
</bean>
<!-- topic目的地配置,其实不管是topic还是queue则他们的底层实现不同但是通过封装api就差不多了,而在spring中更是简单 -->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-topic" />
</bean>
<!-- spring 使用jmsTemplate来实现消息的发送和接受 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="defaultDestination" ref="destinationTopic"></property>
<!-- 进行持久化 -->
<property name="deliveryMode" value="2" />
<!-- 开启订阅模式 -->
<property name="pubSubDomain" value="true" />
</bean>
</beans>
//消息生产者
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"config/xxxx.xml");
// 获取JmsTemplate对象
jt = (JmsTemplate) ctx.getBean("jmsTemplate");
// 调用方法,发送消息
jt.send(new MessageCreator() {
// 消息的产生,返回消息发送消息
public Message createMessage(Session s) throws JMSException {
TextMessage msg = s
.createTextMessage("Spring send msg ----> Hello activeMQ");
return msg;
}
});
System.out.println("end!");
}
消费者配置文件
<!--第三方工厂 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://127.0.0.1:61616" />
<property name="userName" value="admin"></property>
<property name="password" value="admin"></property>
<property name="useAsyncSend" value="true" />
</bean>
<!-- ActiveMQ为我们提供了一个PooledConnectionFactory,通过往里面注入一个ActiveMQConnectionFactory
可以用来将Connection、Session和MessageProducer池化,这样可以大大的减少我们的资源消耗,要依赖于 activemq-pool包 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="connectionFactory" ref="targetConnectionFactory" />
<property name="maxConnections" value="100" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!--消费者标示id -->
<property name="clientId" value="clientId_001" />
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="pooledConnectionFactory" />
</bean>
<!-- topic目的地配置,其实不管是topic还是queue则他们的底层实现不同但是通过封装api就差不多了,而在spring中更是简单 -->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-topic" />
</bean>
<!--消息消费者监听类 -->
<bean id="myMessageListener" class="springs.activemq.Service.MyMessageListener" />
<!--监听容器的配置 -->
<bean id="myListenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<!--消息目的地 -->
<property name="destination" ref="destinationTopic" />
<!--消息监听类 -->
<property name="messageListener" ref="myMessageListener" />
<!-- 发布订阅模式 -->
<property name="pubSubDomain" value="true" />
<!-- 消息持久化值设置为true -->
<property name="subscriptionDurable" value="true" />
<!--消息接收超时 -->
<property name="receiveTimeout" value="10000" />
<!-- 接收者ID -->
<property name="clientId" value="clientId_001" />
<property name="durableSubscriptionName" value="clientId_001" />
</bean>
</beans>
//消息监听类
public class MyMessageListener implements MessageListener {
public void onMessage(Message arg0) {
// TODO Auto-generated method stub
try {
String message = ((TextMessage) arg0).getText();
System.out.println("textmessage:" + message);
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
三种消息监听参考:http://dwj147258.iteye.com/blog/2330295