Spring framework(10):集成 JMS 异步消息队列(ActiveMQ)

JMS 和 Apache-ActiveMQ 简介


JMS(Java Message Service,Java 消息服务)是一个 Java 面向消息中间件(MOM)的 API,用于 Java 应用程序或分布式系统之间发送信息,异步通信;

JMS 具有以下优势

  • 通信的异步性,客户端获取信息不需要主动发送请求,由 JMS 中间件自动推送信息;
  • 消息发送的准确性,JMS 中间件可以保证信息只会发送一次,不会发送重复信息;

JMS 的消息传输模型有以下 2 种


1)点对点模型(P2P,queue)

P2P 模型中,应用程序有消息队列(queue),消息发送者,消息接收者组成,每一个消息发送给一个中间件队列,再由队列发送给单个接收者。具有以下的性质:
  • 每一个消息只有一个接收者(当多个接收者使用同一个消息队列时,接收消息是竞争式的);
  • 发布者和接收者没有时间依赖,当消息发送者发送消息时,即使但是接收者不运行,当只要接收者上线,就可以接收到信息;
  • 当接收者接收到消息时,会发送确认通知;

2)发布/订阅模型(topic)


分布/订阅模型,应 用程序有主题(topic),消息发送者,一系列的消息接收者组成,topic 用于保存和传递消息,并且会一直保存到被传递到客户端。具有以下特性:
  • 一个消息可以传递给多个消息接收者(当多个接收者接收到同一个消息时,每一个接收者都会接收到一份该消息的拷贝);
  • 发布者和接收者有时间依赖,只有当客户端创建订阅后,才可以开始接收到消息(而且是当前开始消息发送者发布的消息),同时订阅者需要一直保持活动状态才可以接收消息;
  • JMS 允许发布订阅者创建一个可持久化的订阅,这样即使订阅者没有上线,也可以在上线后接收到消息;

JMS 这是一个平台无关的 Java API,由 MOM 提供商对其进行支持,这里介绍其中比较轻量化,高效的 Apache-ActiveMQ;
ActiveMQ 项目官网: http://activemq.apache.org

ActiveMQ的安装和部署


由于 JMS 消息中间是独立于客户端,服务端程序的,在运行程序前要先部署好 JMS 服务程序;
Active-MQ 下载地址: http://activemq.apache.org/download.html

选择合适的系统版本下载,解压后,window 版本X64运行 apache-activemq/bin/win64/activemq.bat Unix 版本运行   apache-activemq/bin/activemq.sh,即可启动 ActiveMQ; 

ActiveMQ 提供了一个基于内嵌 Jetty 的管理程序,可以在启动 acgtivemq 后访问服务器的 http://127.0.0.1:8161/admin 访问该管理页面(默认密码:admin,admin)

如果要增加、修改 ActiveMQ 的程序用户认证,可以在 apache-activemq/conf/activemq.xml 文件中的<broker>节点下添加:
 
<broker>
    ....
     <!--增加用户认证-->
        <plugins>
            <simpleAuthenticationPlugin>
                <users>
                    <authenticationUser username="assad" password="assad123" groups="admins" />
                </users>
            </simpleAuthenticationPlugin>
        </plugins>
</broker>
要增加、修改程序访问的 openwire 路由端口,可以修改<transportConnectors >中相关的<transportConnector>节点:
 
   <transportConnectors>
            <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
            <transportConnector name="openwire" uri="tcp://127.0.0.1:23333?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
        ......
  </transportConnectors>

ActiveMQ 支持 3 种类型的数据传输对象:
  • TextMessager:传输文本对象;
  • MapMessager:传输 Map 类型对象;
  • ObjectMessager:传输其他 Object,调用该 Object 的序列化方法序列化为二进制字节传输;

关于使用 Java  编写基于 ActiveMQ 的 JMS 基本程序,可以参考: https://www.cnblogs.com/jaycekon/p/6225058.html
但由于直接用 Java 直接编写步骤比较繁琐,可以使用 Spring 的集成,相对简单很多,详见下面:




Spring 集成 JMS(ActiveMQ)


Spring 提供的 org.springframework:spring-jms 包对 JMS 进行了封装,使用 Spring 风格的 JmsTemplate 封装了 Adminstred Object(管理对象)、ConnectionFactory(连接工厂)、Session(会话)、Destination(目的地)等的配置;

以下示例完整代码地址: https://gitee.com/assad/jms-spring-sample

异步模型快速示例(queue/topic)

示例代码位于以上地址的 basic_sample 路径下;
需要导入的相关依赖包:
 
//spring_sample 核心依赖
compile 'org.springframework:spring-core:4.3.11.RELEASE'
compile 'org.springframework:spring-beans:4.3.11.RELEASE'
//Apache-ActiveMQ ( JMS API 的一种思想) 依赖,Spring 相关支持依赖
compile 'org.apache.activemq:activemq-all:5.15.3'
compile 'org.apache.activemq:activemq-pool:5.15.3'
compile 'org.springframework:spring-jms:4.3.11.RELEASE'

服务端

在 spring 上下文配置文件中进行 ActiveMQ 相关的配置: basic_sample/appContext-server.xml
 
    <!--ActiveMQ 相关配置-->
    <!--第三方工厂-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
          p:brokerURL="tcp://127.0.0.1:23333"
          p:userName="assad"
          p:password="assad123"
          p:trustAllPackages="true"
          p:useAsyncSend="true"/>
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"
          p:connectionFactory-ref="targetConnectionFactory"
          p:maxConnections="100" />
    <!--spring 管理真正 ConnectionFactory 的连接工厂,使用PooledConnectionFactory 封装过的 targetConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"
          p:targetConnectionFactory-ref="pooledConnectionFactory"/>
    <!--目的地配置: queue(point-to-point模式) -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-queue" />
    </bean>
    <!--目的地配置:Topic(发布/订阅模式)-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-topic" />
    </bean>
    <!--配置 Spring jsmTemplate 模板,用于发送和接收消息 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"
          p:connectionFactory-ref="connectionFactory">
        <!--配置默认目的地(queue 或 topic)-->
        <property name="defaultDestination" ref="destinationQueue" />
        <!--使用spring提供的默认消息转换器,也可以装载自己实现的消息转化器-->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>
要上配置文件配置使用 P2P 模型作为目的地,如果要更改为 Topic 模型,只需要丢该 jmsTemplate bean 节点 defaultDestination-ref 属性为之前配置好的 "destinationTpoic " 即可,同样可以在服务端发送消息代码中手动指定目的地;

服务端程序 sample/MessageProducer  :
 
package basic_sample.server;
import bean.User;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
//演示各种类型消息的发送
public class MessageProducer {
    private static JmsTemplate jmsTemplate = null;
    private static final Logger log = LogManager.getLogger();
    public static void main(String[] args){
        
        //获取 JmsTemplate 对象
        jmsTemplate = new ClassPathXmlApplicationContext("basic_sample/appContext-server.xml").getBean("jmsTemplate",JmsTemplate.class);
        //发送文本信息
        String message = "basic_sample.server: Hello world!";
        jmsTemplate.convertAndSend(message);
        log.debug(message);
        //发送 Map 类型信息
        Map<String,Integer> info = new HashMap<>();
        info.put("Guangdong",64815);
        info.put("Jiangshu",62604);
        info.put("Shangdong",54868);
        info.put("Zhijiang",36958);
        jmsTemplate.convertAndSend(info);
        log.debug(info.keySet().stream().map(key -> key+":"+info.get(key)).collect(Collectors.joining(", ")));
        //发送 Object 类型信息(User对象)
        User user = new User();
        user.setId(20137);
        user.setName("assad");
        user.setCity("Guangzhou");
        jmsTemplate.convertAndSend(user);
        log.debug(user);
    }
}
注意在传输对象时,spring 配置文件中的 connectionFactory 的 trustAllPackages 参数需要设置为 true;

客户端

在这个示例中客户端的 Spring 上下文配置文件 basic_sample/appContext-clinet.xml 
 
    <!--ActiveMQ 相关配置-->
    <!--第三方工厂-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
          p:brokerURL="tcp://127.0.0.1:23333"
          p:userName="assad"
          p:password="assad123"
          p:trustAllPackages="true" />
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"
          p:connectionFactory-ref="targetConnectionFactory"
          p:maxConnections="100" />
    <!--spring 管理真正 ConnectionFactory 的连接工厂,使用PooledConnectionFactory 封装过的 targetConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"
          p:targetConnectionFactory-ref="pooledConnectionFactory"/>
    <!--目的地配置: queue(point-to-point模式) -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-queue" />
    </bean>
    <!--目的地配置:Topic(发布/订阅模式)-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-topic" />
    </bean>
    <!--配置 Spring jsmTemplate 模板,用于发送和接收消息 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"
          p:connectionFactory-ref="connectionFactory">
        <!--配置默认目的地(queue 或 topic)-->
        <property name="defaultDestination" ref="destinationQueue" />
        <!--使用spring提供的默认消息转换器,也可以装载自己实现的消息转化器-->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>

客户端程序, basic_sample/client/MessageConsumer
 
package basic_sample.client;
import bean.User;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
//演示接收各种类型的消息
public class MessageConsumer {
    private static JmsTemplate jmsTemplate = null;
    private static final Logger log = LogManager.getLogger();
    public static void main(String[] args){
        //创建 JmsTemplate 对象
        jmsTemplate = new ClassPathXmlApplicationContext("basic_sample/appContext-client.xml").getBean("jmsTemplate",JmsTemplate.class);
        //接收文本对象
        String msg = (String) jmsTemplate.receiveAndConvert();
        log.debug(msg);
        //接收 Map 对象
        Map<String,Integer> info = (HashMap<String,Integer>) jmsTemplate.receiveAndConvert();
        log.debug(info.keySet().stream().map(key -> key+":"+info.get(key)).collect(Collectors.joining(", ")));
        //接收 Object 类型对象
        User user = (User) jmsTemplate.receiveAndConvert();
        log.debug(user);
    }
}

运行步骤,启动 activemq 后,分别运行客户端,服务端;客户端接收的信息如下:


关于 queue 模式和 topic 模式客户端接收信息的区别,可以分别修改  jmsTemplate 的  defaultDestination 参数,运行 basic_sample/MessageProducer2 和 basic_sample/MessageConsumer2 进行测试,结果和以上介绍 JMS 的 2 种传输模型相符合;



客户端使用监听器的方式接收信息

以上示例的客户端是使用 JmsTemplate 接收消息,如果客户端需要使用 监听器的方式 接收信息,可以如下步骤:
创建一个监听器 client/MyListener
 
package persistence_sample.client;
import javax.jms.*;
public class MyListener implements MessageListener {
    private static final Logger log = LogManager.getLogger();
    @Override
    public void onMessage(Message message) {
        try {
            //接收文本对象
            String msg = (String) jmsTemplate.receiveAndConvert();
            log.debug(msg);
            //接收 Map 对象
            Map<String,Integer> info = (HashMap<String,Integer>) jmsTemplate.receiveAndConvert();
            log.debug(info.keySet().stream().map(key -> key+":"+info.get(key)).collect(Collectors.joining(", ")));
            //接收 Object 类型对象
            User user = (User) jmsTemplate.receiveAndConvert();
            log.debug(user);
            
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

在 spring 上下文中配置文件 appContext-client.xml 中配置监听器:
 
    <!--ActiveMQ 相关配置-->
    <!--第三方工厂-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
          p:brokerURL="tcp://127.0.0.1:23333"
          p:userName="assad"
          p:password="assad123"
          p:trustAllPackages="true" />
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"
          p:connectionFactory-ref="targetConnectionFactory"
          p:maxConnections="100" />
    <!--spring 管理真正 ConnectionFactory 的连接工厂,使用PooledConnectionFactory 封装过的 targetConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"
          p:targetConnectionFactory-ref="pooledConnectionFactory"/>
    <!--目的地配置: queue(point-to-point模式) -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-queue" />
    </bean>
     <!--装载自定义消息监听器-->
    <bean id="myTopicListener" class="persistence_sample.client.TopicListener" />
    <!--消息监听容器-->
    <bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"
          p:connectionFactory-ref="connectionFactory"
          p:destination-ref="destinationQueue"
          p:receiveTimeout="1000"
          p:messageListener-ref="myTopicListener">
        <!--messageListener-ref 配置消息监听器-->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>
创建客户端 client/StartReciver
 
public class StartReceiver {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("persistence_sample/appContext-client.xml");
        while(true){
        }
    }
}



Topic 消息持久化配置

topic 非持久化消息订阅会持续到订阅对象的生命周期,由于 topic 模型的实际产生场景一般会有多个订阅对象,网络 IO 开销会很大,ActiveMQ 提供了持久化实现,可以将 topic 消息储存到磁盘文件或者数据库的实现,如果持久化没有活动的订阅者,JMS 会保存该订阅消息,直到消息被订阅或过期;

以下是一个实现示例,代码模块在示例代码地址的 persistence 目录下;

服务端

spring 配置文件: persistence_sample/appContext-server.xml
 
 <!--ActiveMQ 相关配置-->
    <!--第三方工厂-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
          p:brokerURL="tcp://127.0.0.1:23333"
          p:userName="assad"
          p:password="assad123"
          p:trustAllPackages="true" />
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"
          p:connectionFactory-ref="targetConnectionFactory"
          p:maxConnections="100" />
    <!--spring 管理真正 ConnectionFactory 的连接工厂,使用PooledConnectionFactory 封装过的 targetConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"
          p:targetConnectionFactory-ref="pooledConnectionFactory"/>
    <!--目的地配置:queue(发布/订阅模式)-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-topic-persistence" />
    </bean>
    <!--配置 Spring jsmTemplate 模板,配置为 Topic 模式 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"
          p:connectionFactory-ref="connectionFactory"
          p:defaultDestination-ref="destinationTopic"
          p:pubSubDomain="true"
          p:sessionAcknowledgeMode="1"
          p:explicitQosEnabled="true"
          p:deliveryMode="2"
          p:receiveTimeout="10000">
        <!--以上配置中,explicitQosEnabled用于生效deliveryMode配置
            deliveryMode 为发送模式,1 为非持久,2 为持久-->
        <!--使用spring提供的默认消息转换器-->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>
服务端程序  persistence_sample/server/MessageProducer
 
ackage persistence_sample.server;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
public class MessageProducer {
    private static JmsTemplate jmsTemplate ;
    private static final Logger log = LogManager.getLogger();
    public static void main(String[] args) throws InterruptedException {
        jmsTemplate = new ClassPathXmlApplicationContext("persistence_sample/appContext-server.xml").getBean("jmsTemplate",JmsTemplate.class);
        //发送20条信息
        for(int i=1; i<=20; i++){
            String message = String.format("Message@<%d>",i);
            jmsTemplate.convertAndSend(message);
            log.debug(message);
            Thread.sleep(900);
        }
    }
}

客户端

示例代码客户端使用监听器的方式实现获取消息;
spring 配置文件: persistence_sample/appContext-client.xml
 
    <!--ActiveMQ 相关配置-->
    <!--第三方工厂-->
    <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
          p:brokerURL="tcp://127.0.0.1:23333"
          p:userName="assad"
          p:password="assad123"
          p:trustAllPackages="true" />
    <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"
          p:connectionFactory-ref="targetConnectionFactory"
          p:maxConnections="100" />
    <!--spring 管理真正 ConnectionFactory 的连接工厂,使用PooledConnectionFactory 封装过的 targetConnectionFactory -->
    <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"
          p:clientId="client_001"
          p:targetConnectionFactory-ref="pooledConnectionFactory"/>
    <!--目的地配置:Topic(发布/订阅模式)-->
    <bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
        <constructor-arg index="0" value="spring-topic-persistence" />
    </bean>
    <!--装载自定义消息监听器-->
    <bean id="myTopicListener" class="persistence_sample.client.TopicListener" />
    <!--消息监听容器-->
    <bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"
          p:connectionFactory-ref="connectionFactory"
          p:destination-ref="destinationTopic"
          p:pubSubDomain="true"
          p:subscriptionDurable="true"
          p:clientId="client_001"
          p:durableSubscriptionName="client_001"
          p:receiveTimeout="1000"
          p:messageListener-ref="myTopicListener">
        <!--pubSubDomain开启订阅模式,subscriptionDurable设置允许持久化,
        clientId、durableSubscriptionName 设置接收者ID-->
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter" />
        </property>
    </bean>
消息监听器  persistence_sample/client/TopicListener
 
package persistence_sample.client;
import javax.jms.*;
public class TopicListener implements MessageListener {
    private static final Logger log = LogManager.getLogger();
    @Override
    public void onMessage(Message message) {
        try {
            String msg = ((TextMessage)message).getText();
            log.debug(msg);
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}
客户端启动程序  persistence_sample/client/StartReceiver
 
package persistence_sample.client;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.context.ApplicationContext;
public class StartReceiver {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("persistence_sample/appContext-client.xml");
        while(true){
        }
    }
}

ActiveMQ 的配置

具体持久化的实现实在 ActiveMQ 中进行配置的,ActiveMQ 支持以下几种持久化方式:
  • AMQ 文件储存;
  • KahaDB 文件储存;
  • LevelDB 储存
  • JDBC 数据库持久化;
这里演示使用  KahaDB 文件作为持久化文件储存格式,在 apache-activemq/conf/activemq.xml 中<brokler>节点下添加以下:
 
<broker>
    ....
    <!--持久化配置-->
    <persistenceAdapter>
            <kahaDB directory="${activemq.data}/kahadb"/>
        </persistenceAdapter>
    ....
</broker>

关于ActiveMQ 详细的持久化配置,参考: https://my.oschina.net/u/1455908/blog/310115





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值