最近在学习Java消息服务(JMS),Java消息服务主要是用在应用间解耦。比如,我们经常用到的短信服务,一笔交易完成之后,我们可能需要发送短信,为了加快系统响应速度,我们可能需要把短信设计成异步的,我们只需要把短信发送给短信平台,最终短信是否成功交由短信平台来处理。另外一种使用场景就是支付交易,一笔支付交易完成之后,支付平台需要后台异步通知商户,支付已经成功。这个通知通常也会做成异步的。像以上两种使用场景,我们就可以使用Java消息服务来实现。
Java消息服务(JMS),只是一组规范,即API。它的具体实现由厂商自己去实现,就像JDBC一样。目前使用较多的实现有ActiveMQ,RabbitMQ,kafuka。接下来主要介绍ActiveMQ的一些基本概念以及与Spring的整合。
JMS分为两种消息传送模式,点到点模式(P2P),发布订阅模式(Pub/Sub)模式。点到点模式组成包括:消息发送者,接受者,JMS提供者(broken服务器);P2P消息目的地称为队列Queue,且消息只能被一个接受者接受。发布订阅模式组成包括:发布者,订阅者,JMS提供者(broken服务器),发布订阅的目的地称为主题Topic,且消息能够被多个订阅者接受。
接下来主要是介绍Spring与ActiveMQ的结合使用,主要介绍P2P消息队列。把消息发送者,消息接收者放在两个应用,使用外部ActiveMQ。
1、新建JMS消息发送者应用,ActiveMQ_Producer。applicationContext.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 连接池 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<!-- 连接工厂 -->
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
<property name="maxConnections" value="10"/>
</bean>
<!-- 消息模板 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="pooledConnectionFactory" />
<property name="defaultDestination" ref="queueDestination" />
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
</property>
<!-- 消息转换器 -->
<!-- <property name="messageConverter" ref="emailMessageConverter"/> -->
</bean>
<!-- 配置消息目标 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 目标,在ActiveMQ管理员控制台创建 http://localhost:8161/admin/queues.jsp -->
<constructor-arg index="0" value="helloQueue" />
</bean>
<!-- 用于测试消息回复的 -->
<bean id="responseQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>responseQueue</value>
</constructor-arg>
</bean>
<!-- 消息监听器 -->
<bean id="responseMessageListener" class="com.hua.spring.jms.listener.ResponseQueueMessageListener"/>
<!-- 消息监听容器 -->
<bean id="responseQueueListenerAdapter" class=" org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="pooledConnectionFactory" />
<property name="destination" ref="responseQueue" />
<property name="messageListener" ref="responseMessageListener" />
<property name="sessionTransacted" value="false"/>
</bean>
</beans>
主要有两个队列,一个是发送队列queueDestination,一个是用来接收响应的队列responseQueue。采用异步接受消息,所以接收响应的队列responseQueue需要配置监听器,并把监听器放入监听容器。监听器需要实现MessageListener接口,或者SessionAwareMessageListener接口。
2、新建监听器类:ResponseQueueMessageListener.java
package com.hua.spring.jms.listener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @description
* @date:(2016-8-25 下午10:01:07)
* @author Administrator
* @version v1.0
* @since v1.0
*
* Modified history
*
* Modified date:
* Modifier user:
* description:
*
* */
public class ResponseQueueMessageListener implements MessageListener{
private Logger logger=LoggerFactory.getLogger(ResponseQueueMessageListener.class);
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
try {
String text=((TextMessage) message).getText();
logger.info("消息生产者接收到响应:"+text);
} catch (JMSException e) {
logger.error("消息生产者接收消息时发生异常:",e);
}
}
}
}
监听器类实现了MessageListener接口,这个接口只有一个方法public void onMessage(Message message)。
3、新建消息提供者启动类Main.java
package com.jms.producer.client;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
/**
* @description
* @date:(2016-8-28 上午9:43:03)
* @author Administrator
* @version v1.0
* @since v1.0
*
* Modified history
*
* Modified date:
* Modifier user:
* description:
*
* */
public class Main {
private static Logger logger=LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext(
"/config/applicationContext.xml");
JmsTemplate jmsTemplate=(JmsTemplate)context.getBean("jmsTemplate");
jmsTemplate.send("helloQueue", new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
String msg="发送者:我是消息生产者";
TextMessage textMessage=session.createTextMessage(msg);
logger.info(msg);
return textMessage;
}
});
}
}
4、另外日志输出配置文件logback.xml如下:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder 默认配置为PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
项目目录结构如下:
5、新建消息接受者应用ActiveMQ_Consumer,applicationContext.xml如下:
<pre name="code" class="html"><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 连接池 -->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<!-- 连接工厂 -->
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</property>
<property name="maxConnections" value="10"/>
</bean>
<!-- 消息模板 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="pooledConnectionFactory" />
<property name="defaultDestination" ref="queueDestination" />
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
</property>
<!-- 消息转换器 -->
<!-- <property name="messageConverter" ref="emailMessageConverter"/> -->
</bean>
<!-- 配置消息目标 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 目标,在ActiveMQ管理员控制台创建 http://localhost:8161/admin/queues.jsp -->
<constructor-arg index="0" value="helloQueue" />
</bean>
<!-- 用于测试消息回复的 -->
<bean id="responseQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>responseQueue</value>
</constructor-arg>
</bean>
<!-- 可以获取session的MessageListener -->
<bean id="queueDestinationMessageListener" class="com.hua.spring.jms.listener.QueueDestinationMessageListener">
<property name="destination" ref="responseQueue"/>
</bean>
<!-- 消息监听容器 -->
<bean id="responseQueueListenerAdapter" class=" org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="pooledConnectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="queueDestinationMessageListener" />
<property name="sessionTransacted" value="false"/>
</bean>
</beans>
消息接受者配置也是两个队列,其中要对消息发送者队列queueDestination进行监听,配置监听器并把监听器注册到监听容器。
6、新建监听器类QueueDestinationMessageListener.java。实现SessionAwareMessageListener接口,这样就可以对消息进行相应响应,不需要进行响应时可实现MessageListener接口。则不能进行
package com.hua.spring.jms.listener;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.listener.SessionAwareMessageListener;
/**
* @description
* @date:(2016-8-28 上午9:49:32)
* @author Administrator
* @version v1.0
* @since v1.0
*
* Modified history
*
* Modified date:
* Modifier user:
* description:
*
* */
public class QueueDestinationMessageListener implements SessionAwareMessageListener<Message>{
private Logger logger=LoggerFactory.getLogger(QueueDestinationMessageListener.class);
private Destination destination;
@Override
public void onMessage(Message message, Session session) throws JMSException {
if(message instanceof TextMessage){
try {
String text=((TextMessage)message).getText();
logger.info("接受者:我收到消息-->"+text);
MessageProducer producer = session.createProducer(destination);
Message textMessage = session.createTextMessage("我是消息接收者,我已接收到消息");
producer.send(textMessage);
} catch (JMSException e) {
logger.error("监听消息发生异常",e);
}
}
}
public Destination getDestination() {
return destination;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
}
7、新建消息接受者应用的启动类:
package com.jms.client;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @description
* @date:(2016-8-28 上午10:27:43)
* @author Administrator
* @version v1.0
* @since v1.0
*
* Modified history
*
* Modified date:
* Modifier user:
* description:
*
* */
public class Consumer {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(
"/config/applicationContext.xml");
}
}
整体项目结构如下:
下载ActiveMQ,我用到的是apache-activemq-5.8.0,解压后如下。
进入到lib目录,如果是window环境,就点击activemq.bat;Linux环境就执行activemq这样就可以启动broken服务器了。
执行Main.java和Consumer.java可以看到如下日志:
ActiveMQ_Producer的日志:
ActiveMQ_Consumer的日志: