当前环境

  1. Mac OS 10.11.x
  2. docker 1.12.1
  3. JDK 1.8
  4. SpringBoot 1.5

前言

基于之前一篇“一个故事告诉你什么是消息队列”,了解了消息队列的使用场景以及相关的特性。本文主要讲述消息服务在 JAVA 中的使用。

市面上的有关消息队列的技术选型非常多,如果我们的代码框架要支持不同的消息实现,在保证框架具有较高扩展性的前提下,我们势必要进行一定的封装。

在 JAVA 中,大可不必如此。因为 JAVA 已经制定了一套标准的 JMS 规范。该规范定义了一套通用的接口和相关语义,提供了诸如持久、验证和事务的消息服务,其最主要的目的是允许Java应用程序访问现有的消息中间件。就和 JDBC 一样。

基本概念

在介绍具体的使用之前,先简单介绍一下 JMS 的一些基本知识。这里我打算分为 3 部分来介绍,即 消息队列(MQ)的连接、消息发送与消息接收。

这里我们的技术选型是 SpringBoot、JMS、ActiveMQ

为了更好的理解 JMS,这里没有使用 SpringBoot 零配置来搭建项目

MQ 的连接

使用 MQ 的第一步一定是先连接 MQ。因为这里使用的是 JMS 规范,对于任何遵守 JMS 规范的 MQ 来说,都会实现相应的ConnectionFactory接口,因此我们只需要创建一个ConnectionFactory工厂类,由它来实现 MQ 的连接,以及封装一系列特性的 MQ 参数。

例子:这里我们以 ActiveMQ 为例,

maven 依赖:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
</dependencies>

创建 ActiveMQ 连接工厂:

@Bean
public ConnectionFactory connectionFactory(){ ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(ActiveMQ_URL);
connectionFactory.setUserName(ActiveMQ_USER);
connectionFactory.setPassword(ActiveMQ_PASSWORD);
return connectionFactory; }

消息发送

关于消息的发送,是通过 JMS 核心包中的JmsTemplate类来实现的,它简化了 JMS 的使用,因为在发送或同步接收消息时它帮我们处理了资源的创建和释放。从它的作用也不难推测出,它需要引用我们上面创建的连接工厂,具体代码如下:

@Bean
public JmsTemplate jmsQueueTemplate(){ return new JmsTemplate(connectionFactory()); }

JmsTemplate创建完成后,我们就可以调用它的方法来发送消息了。这里有两个概念需要注意:

  1. 消息会发送到哪里?-> 即需要指定发送队列的目的地(Destination),是可以在 JNDI 中进行存储和提取的 JMS 管理对象。
  2. 发送的消息体具体是什么?-> 实现了javax.jms.Message的对象,类似于 JAVA RMI 的 Remote 对象。

代码示例:

@Autowired
private JmsTemplate jmsQueueTemplate; /**
* 发送原始消息 Message
*/
public void send(){ jmsQueueTemplate.send("queue1", new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("我是原始消息");
}
}); }

优化:当然,我们不用每次都通过MessageCreator匿名类的方式来创建Message对象,JmsTemplate类中提供了对象实体自动转换为Message对象的方法,convertAndSend(String destinationName, final Object message)

优化代码示例:

/**
* 发送消息自动转换成原始消息
*/
public void convertAndSend(){ jmsQueueTemplate.convertAndSend("queue1", "我是自动转换的消息"); }

注:关于消息转换,还可以通过实现MessageConverter接口来自定义转换内容

消息接收

讲完了消息发送,我们最后来说说消息是如何接收的。消息既然是以Message对象的形式发送到指定的目的地,那么消息的接收势必会去指定的目的地上去接收消息。这里采用的是监听者的方式来监听指定地点的消息,采用注解@JmsListener来设置监听方法。

代码示例:

@Component
public class Listener1 { @JmsListener(destination = "queue1")
public void receive(String msg){
System.out.println("监听到的消息内容为: " + msg);
} }

有了监听的目标和方法后,监听器还得和 MQ 关联起来,这样才能运作起来。这里的监听器可能不止一个,如果每个都要和 MQ 建立连接,肯定不太合适。所以需要一个监听容器工厂的概念,即接口JmsListenerContainerFactory,它会引用上面创建好的与 MQ 的连接工厂,由它来负责接收消息以及将消息分发给指定的监听器。当然也包括事务管理、资源获取与释放和异常转换等。

代码示例:

@Bean
public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() { DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
//设置连接数
factory.setConcurrency("3-10");
//重连间隔时间
factory.setRecoveryInterval(1000L);
return factory; }

场景

代码地址:https://github.com/jasonGeng88/springboot-jms

对 JMS 有了基本的理解后,我们就来在具体的场景中使用一下。

首先,我们需要先启动 ActiveMQ,这里我们以 Docker 容器化的方式进行启动。

启动命令:

docker run -d -p 8161:8161 -p 61616:61616 --name activemq webcenter/activemq

启动成功后,在 ActiveMQ 可视化界面查看效果(http://localhost:8161):


点对点模式(单消费者)

下面介绍消息队列中最常用的一种场景,即点对点模式。基本概念如下:

  1. 每个消息只能被一个消费者(Consumer)进行消费。一旦消息被消费后,就不再在消息队列中存在。
  2. 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列。
  3. 接收者在成功接收消息之后需向队列应答成功。

代码实现(为简化代码,部分代码沿用上面所述):

  • 启动文件(Application.java)
@SpringBootApplication
@EnableJms
public class Application { ... /**
* JMS 队列的模板类
* connectionFactory() 为 ActiveMQ 连接工厂
*/
@Bean
public JmsTemplate jmsQueueTemplate(){
return new JmsTemplate(connectionFactory());
} public static void main(String[] args) {
SpringApplication.run(Application.class, args);
} }

注解@EnableJms设置在@Configuration类上,用来声明对 JMS 注解的支持。

  • 消息生产者(PtpProducer.java)
@Component
public class PtpProducer { @Autowired
private JmsTemplate jmsQueueTemplate; /**
* 发送消息自动转换成原始消息
*/
public void convertAndSend(){
jmsQueueTemplate.convertAndSend("ptp", "我是自动转换的消息");
}
}
  • 生产者调用类(PtpController.java)
@RestController
@RequestMapping(value = "/ptp")
public class PtpController { @Autowired
private PtpProducer ptpProducer; @RequestMapping(value = "/convertAndSend")
public Object convertAndSend(){
ptpProducer.convertAndSend();
return "success";
} }
  • 消息监听容器工厂
@SpringBootApplication
@EnableJms
public class Application { ... /**
* JMS 队列的监听容器工厂
*/
@Bean(name = "jmsQueueListenerCF")
public DefaultJmsListenerContainerFactory jmsQueueListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
//设置连接数
factory.setConcurrency("3-10");
//重连间隔时间
factory.setRecoveryInterval(1000L);
return factory;
} ... }
  • 消息监听器
@Component
public class PtpListener1 { /**
* 消息队列监听器
* destination 队列地址
* containerFactory 监听器容器工厂, 若存在2个以上的监听容器工厂,需进行指定
*/
@JmsListener(destination = "ptp", containerFactory = "jmsQueueListenerCF")
public void receive(String msg){ System.out.println("点对点模式1: " + msg); }
}

演示

启动项目启动后,通过 REST 接口的方式来调用消息生产者发送消息,请求如下:

curl -XGET 127.0.0.1:8080/ptp/convertAndSend

消费者控制台信息:

ActiveMQ 控制台信息:

列表说明:

  • Name:队列名称。
  • Number Of Pending Messages:等待消费的消息个数。
  • Number Of Consumers:当前连接的消费者数目,因为我们采用的是连接池的方式连接,初始连接数为 3,所以显示数字为 3。
  • Messages Enqueued:进入队列的消息总个数,包括出队列的和待消费的,这个数量只增不减。
  • Messages Dequeued:出了队列的消息,可以理解为是已经消费的消息数量。

点对点模式(多消费者)

基于上面一个消费者消费的模式,因为生产者可能会有很多,同时像某个队列发送消息,这时一个消费者可能会成为瓶颈。所以需要多个消费者来分摊消费压力(消费线程池能解决一定压力,但毕竟在单机上,做不到分布式分布,所以多消费者是有必要的),也就产生了下面的场景。

代码实现

  • 添加新的监听器
@Component
public class PtpListener2 { @JmsListener(destination = Constant.QUEUE_NAME, containerFactory = "jmsQueueListenerCF")
public void receive(String msg){ System.out.println("点对点模式2: " + msg); }
}

演示

这里我们发起 10 次请求,来观察消费者的消费情况:

这里因为监听容器设置了线程池的缘故,在实际消费过程中,监听器消费的顺序会有所差异。

发布订阅模式

除了点对点模式,发布订阅模式也是消息队列中常见的一种使用。试想一下,有一个即时聊天群,你在群里发送一条消息。所有在这个群里的人(即订阅了该群的人),都会收到你发送的信息。

基本概念:

  1. 每个消息可以有多个消费者。
  2. 发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
  3. 为了消费消息,订阅者必须保持运行的状态。

代码实现

  • 修改 JmsTemplate 模板类,使其支持发布订阅功能
@SpringBootApplication
@EnableJms
public class Application { ... @Bean
public JmsTemplate jmsTopicTemplate(){
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
jmsTemplate.setPubSubDomain(true);
return jmsTemplate;
} ... }
  • 消息生产者(PubSubProducer.java)
@Component
public class PtpProducer { @Autowired
private JmsTemplate jmsTopicTemplate; public void convertAndSend(){
jmsTopicTemplate.convertAndSend("topic", "我是自动转换的消息");
}
}
  • 生产者调用类(PubSubController.java)
@RestController
@RequestMapping(value = "/pubsub")
public class PtpController { @Autowired
private PubSubProducer pubSubProducer; @RequestMapping(value = "/convertAndSend")
public String convertAndSend(){
pubSubProducer.convertAndSend();
return "success";
} }
  • 修改 DefaultJmsListenerContainerFactory 类,使其支持发布订阅功能
@SpringBootApplication
@EnableJms
public class Application { ... /**
* JMS 队列的监听容器工厂
*/
@Bean(name = "jmsTopicListenerCF")
public DefaultJmsListenerContainerFactory jmsTopicListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrency("1");
factory.setPubSubDomain(true);
return factory;
} ... }
  • 消息监听器(这里设置2个订阅者)
@Component
public class PubSubListener1 { @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF")
public void receive(String msg){
System.out.println("订阅者1 - " + msg);
}
} @Component
public class PubSubListener2 { @JmsListener(destination = "topic", containerFactory = "jmsTopicListenerCF")
public void receive(String msg){
System.out.println("订阅者2 - " + msg);
}
}

演示

curl -XGET 127.0.0.1:8080/pubSub/convertAndSend

消费者控制台信息:

ActiveMQ 控制台信息:

总结

这里只是对 SpringBoot 与 JMS 集成的简单说明与使用,详细的介绍可以查看 Spring 的官方文档,我这里也有幸参与 并发编程网 发起的 Spring 5 的翻译工作,我主要翻译了 Spring 5 的 JMS 章节,其内容对于上述 JMS 的基本概念,都有详细的展开说明,有兴趣的可以看一下,当然翻译水平有限,英文好的建议看原文。

JMS 在 SpringBoot 中的使用的更多相关文章

  1. 以ActiveMQ为例JAVA消息中间件学习【3】——SpringBoot中使用ActiveMQ

    前言 首先我们在java环境中使用了ActiveMQ,然后我们又在Spring中使用了ActiveMQ 本来这样已经可以了,但是最近SpringBoot也来了.所以在其中也需要使用试试. 可以提前透露 ...

  2. SpringBoot中yaml配置对象

    转载请在页首注明作者与出处 一:前言 YAML可以代替传统的xx.properties文件,但是它支持声明map,数组,list,字符串,boolean值,数值,NULL,日期,基本满足开发过程中的所 ...

  3. 如何在SpringBoot中使用JSP ?但强烈不推荐,果断改Themeleaf吧

    做WEB项目,一定都用过JSP这个大牌.Spring MVC里面也可以很方便的将JSP与一个View关联起来,使用还是非常方便的.当你从一个传统的Spring MVC项目转入一个Spring Boot ...

  4. springboot中swaggerUI的使用

    demo地址:demo-swagger-springboot springboot中swaggerUI的使用 1.pom文件中添加swagger依赖 2.从github项目中下载swaggerUI 然 ...

  5. spring-boot+mybatis开发实战:如何在spring-boot中使用myabtis持久层框架

    前言: 本项目基于maven构建,使用mybatis-spring-boot作为spring-boot项目的持久层框架 spring-boot中使用mybatis持久层框架与原spring项目使用方式 ...

  6. 由浅入深学习springboot中使用redis

    很多时候,我们会在springboot中配置redis,但是就那么几个配置就配好了,没办法知道为什么,这里就详细的讲解一下 这里假设已经成功创建了一个springboot项目. redis连接工厂类 ...

  7. Springboot中使用AOP统一处理Web请求日志

    title: Springboot中使用AOP统一处理Web请求日志 date: 2017-04-26 16:30:48 tags: ['Spring Boot','AOP'] categories: ...

  8. SpringBoot 中常用注解

    本篇博文将介绍几种SpringBoot 中常用注解 其中,各注解的作用为: @PathVaribale 获取url中的数据 @RequestParam 获取请求参数的值 @GetMapping 组合注 ...

  9. SpringBoot中关于Mybatis使用的三个问题

    SpringBoot中关于Mybatis使用的三个问题 转载请注明源地址:http://www.cnblogs.com/funnyzpc/p/8495453.html 原本是要讲讲PostgreSQL ...

随机推荐

  1. College student reflects on getting started in open source(一)

    I just completed the first semester of my second year in college, and I'm reflecting on what I learn ...

  2. Linuxmint:MySQL安装指南(转贴)

    这篇文章来自以下网站:http://wiki.ubuntu.com.cn/MySQL. 安装MySQL sudo apt-get install mysql-server 这个应该很简单了,而且我觉得 ...

  3. codechef AUG17 T2 Chef and Mover

    Chef and Mover Problem Code: CHEFMOVR Chef's dog Snuffles has so many things to play with! This time ...

  4. jQuery遍历文档(重要)

    什么是遍历? jQuery 遍历,意为"移动",用于根据其相对于其他元素的关系来"查找"(或选取)HTML 元素.以某项选择开始,并沿着这个选择移动,直到抵达您 ...

  5. sqlalchemy多表查询

    from datetime import datetime from sqlalchemy import Column,Integer,String,Boolean,DateTime,ForeignK ...

  6. 非常好!!!---bash转义序列笔记---打印语句printf用法【转】

    转自:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2318684&fromuid=23571134 本教程由 惟吾无为 ...

  7. python--math

    >>> import math >>> >>> # ceil,取大于等于x的最小的整数值 >>> math.ceil(4) 4 ...

  8. Appium+python自动化13-native和webview切换【转载】

    前言 现在大部分app都是混合式的native+webview,对应native上的元素通过uiautomatorviewer很容易定位到,webview上的元素就无法识别了. 一.识别webview ...

  9. 我从16ASPX上下了一个程序在运行时出错是怎么回事?运行时出现用户SA登陆失败,但是我已经把数据库导入SQL

    如果你账号密码正确,那你可能没有打开你的管线服务,或者没有配置好你的客户端

  10. RANSAC中迭代次数的计算

    假设 $w=\frac{内点个数 }{所有点的个数}$. 则 $p_{0}=w^n$表示采样的$n$个点全为内点的概率(可重复) 则至少有一个为外点的概率$p_{1}=1-p_{0}$ 则重复$K$次 ...