In the previous two chapters, Chapter 41: Completing DirectExchange Distributed Message Consumption Based on SpringBoot & RabbitMQ , Chapter 42: Completing DirectExchange Distributed Message Multi-Consumer Consumption Based on SpringBoot & RabbitMQ Improves the exchange type of RabbitMQ
message queues There are three types of exchanges that are commonly used in DirectExchange
our previous chapters RabbitMQ
. Let's take a look at the TopicExchange
topic exchange types today.
Objectives of this chapter
The exchange of message types done based on the SpringBoot
platform .RabbitMQ
TopicExchange
SpringBoot enterprise-level core technology learning topic
Topic | Topic name | Thematic description |
---|---|---|
001 | Spring Boot core technology | Explain some core components of SpringBoot at the enterprise level |
002 | Spring Boot core technology chapter source code | Each article in the Spring Boot core technology brief book corresponds to the source code of the code cloud |
003 | Spring Cloud core technology | Comprehensive explanation of the core technologies of Spring Cloud |
004 | Spring Cloud core technology chapter source code | Each article in the Spring Cloud core technology brief book corresponds to the source code |
005 | QueryDSL core technology | Comprehensively explain the core technology of QueryDSL and integrate SpringDataJPA based on SpringBoot |
006 | SpringDataJPA core technology | Comprehensive explanation of the core technology of Spring Data JPA |
Solve the problem
The teenager also encountered a problem before. After classifying multiple modules, the message queue cannot be automatically created. It is funny to say that I didn’t have time to look at this problem before. When I wrote this article today, I found that the reason was that the configuration class SpringBoot
in the module was not scanned . common
. Make my head big~~~, we can automatically create a queue by XxxApplication
adding it to the startup class ! @ComponentScan(value = "com.hengyu.rabbitmq")
! !
Build the project
When building the project in this chapter, the design is also carried out in a multi-module way. You can see the effect of message processing well. Because it is a multi-module project, let's create a SpringBoot
project first. The pom.xml
configuration file dependency configuration is as follows:
<dependencies>
<!--rabbbitMQ相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--web相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--spring boot tester-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--fast json依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
</dependencies>
Next, let's build public RabbitMQ
modules first, because our consumers and producers need RabbitMQ
relevant configuration information, here we can extract them and use them for reference between modules.
rabbitmq-topic-common
Create a submodule ,rabbitmq-topic-common
add a configuration file and configure the relevant dependency configuration below, as follows:resources
application.yml
RabbitMQ
spring:
#rabbitmq消息队列配置信息
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: true
Define exchange configuration information
Like the previous chapter, we independently write an enumeration type to configure the exchange information of the message queue, as follows:
/**
* rabbitmq交换配置枚举
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:13:56
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum ExchangeEnum
{
/**
* 用户注册交换配置枚举
*/
USER_REGISTER_TOPIC_EXCHANGE("register.topic.exchange")
;
private String name;
ExchangeEnum(String name) {
this.name = name;
}
}
Define queue configuration information
The basic information configuration of the same message queue is also configured in the form of enumeration, as shown below:
/**
* 队列配置枚举
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:05
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum QueueEnum
{
/**
* 用户注册
* 创建账户消息队列
*/
USER_REGISTER_CREATE_ACCOUNT("register.account","register.#"),
/**
* 用户注册
* 发送注册成功邮件消息队列
*/
USER_REGISTER_SEND_MAIL("register.mail","register.#")
;
/**
* 队列名称
*/
private String name;
/**
* 队列路由键
*/
private String routingKey;
QueueEnum(String name, String routingKey) {
this.name = name;
this.routingKey = routingKey;
}
}
Two attributes have been added to the message queue enumeration, corresponding to , respectively 队列名称
. 队列路由
The type of message queue explained in this chapter TopicExchange
can configure multiple message consumers according to the path information, and the forwarding matching rule information is the routing information of the queue we defined. .
Define routing information for sending messages
When we send a message to the queue, we need to pass a routing-related configuration information, which RabbitMQ
will be matched according to the message routing rule information when sending and the routing information when defining the message queue. If it can match, the consumer of the queue will be called to complete the message. The configuration of the consumption and sending message routing information is as follows:
/**
* 消息队列topic交换路由key配置枚举
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:58
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Getter
public enum TopicEnum {
/**
* 用户注册topic路由key配置
*/
USER_REGISTER("register.user")
;
private String topicRouteKey;
TopicEnum(String topicRouteKey) {
this.topicRouteKey = topicRouteKey;
}
}
routing special characters#
There is a special symbol when we configure it QueueEnum
internally : , when routing configuration in the message queue, it means that zero or more characters can be matched, and what we define in the enumeration can match the routing rules of the queue defined by the enumeration . Of course, if the routing is passed when sending a message: it is also a routing rule that can be matched in the same way.路由键
#
RabbitMQ
#
TopicEnum
register.user
QueueEnum
register.#
register.user.account
register.#
routing special characters*
In addition to this, there is one more commonly used special character , which means that a character can be matched when routing configuration *
in the RabbitMQ
message queue . We define that if the routing key is modified to , the message can be received when the routing key is sent. But if the route at the time of sending is time , then the message cannot be matched.*
QueueEnum
register.*
register.user
register.user.account
message queue configuration
The configuration preparations have been completed, and then we start to configure the queue-related content. As before, we need to configure Queue
, Exchange
, Binding
and bind the message queue to the exchange. Let's take a look at the differences between the configuration and the previous chapters. The code is as follows:
/**
* 用户注册消息队列配置
* ========================
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:16:58
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Configuration
public class UserRegisterQueueConfiguration {
private Logger logger = LoggerFactory.getLogger(UserRegisterQueueConfiguration.class);
/**
* 配置用户注册主题交换
* @return
*/
@Bean
public TopicExchange userTopicExchange()
{
TopicExchange topicExchange = new TopicExchange(ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE.getName());
logger.info("用户注册交换实例化成功。");
return topicExchange;
}
/**
* 配置用户注册
* 发送激活邮件消息队列
* 并设置持久化队列
* @return
*/
@Bean
public Queue sendRegisterMailQueue()
{
Queue queue = new Queue(QueueEnum.USER_REGISTER_SEND_MAIL.getName());
logger.info("创建用户注册消息队列成功");
return queue;
}
/**
* 配置用户注册
* 创建账户消息队列
* 并设置持久化队列
* @return
*/
@Bean
public Queue createAccountQueue()
{
Queue queue = new Queue(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getName());
logger.info("创建用户注册账号队列成功.");
return queue;
}
/**
* 绑定用户发送注册激活邮件队列到用户注册主题交换配置
* @return
*/
@Bean
public Binding sendMailBinding(TopicExchange userTopicExchange,Queue sendRegisterMailQueue)
{
Binding binding = BindingBuilder.bind(sendRegisterMailQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_SEND_MAIL.getRoutingKey());
logger.info("绑定发送邮件到注册交换成功");
return binding;
}
/**
* 绑定用户创建账户到用户注册主题交换配置
* @return
*/
@Bean
public Binding createAccountBinding(TopicExchange userTopicExchange,Queue createAccountQueue)
{
Binding binding = BindingBuilder.bind(createAccountQueue).to(userTopicExchange).with(QueueEnum.USER_REGISTER_CREATE_ACCOUNT.getRoutingKey());
logger.info("绑定创建账号到注册交换成功。");
return binding;
}
}
Let's start with the analysis above.
Step 1: First we create the TopicExchange
message queue object, using ExchangeEnum
the type in the enumeration USER_REGISTER_TOPIC_EXCHANGE
as the exchange name.
Step 2: We created the queue to send the registration email sendRegisterMailQueue
, using QueueEnum
the type in the enumeration USER_REGISTER_SEND_MAIL
as the queue name.
Step 3: Consistent with the sending mail queue, the account information needs to be initialized after the user is created, and the createAccountQueue
subsequent logic of the message queue is to complete the work, using QueueEnum
the enumeration in the USER_REGISTER_CREATE_ACCOUNT
enumeration as the name of the account creation queue.
Step 4: The exchange and queue have been created in the above steps, and now the queue is bound to the user registration exchange, so as to realize the message consumption of the registered user message queue and the sendMailBinding
bound configuration information.QueueEnum.USER_REGISTER_SEND_MAIL
RoutingKey
createAccountBinding
Binding configuration information QueueEnum.USER_REGISTER_CREATE_ACCOUNT
.RoutingKey
So far we have completed rabbitmq-topic-common
all the configuration information of the module, let's start to write the user registration message consumer module.
rabbitmq-topic-consumer
Let's first create a submodule named and add a reference to the module rabbitmq-topic-consumer
in the pom.xml
configuration file rabbitmq-topic-common
, as follows:
....//
<dependencies>
<!--公共模块依赖-->
<dependency>
<groupId>com.hengyu</groupId>
<artifactId>rabbitmq-topic-common</artifactId>
<version>${parent.version}</version>
</dependency>
</dependencies>
....//
consumer program entry
Next, let's create the program startup class RabbitMqTopicConsumerApplication
. It should be noted here that the scan path is manually configured . The @ComponentScan
startup class code is as follows:
/**
* 消息消费者程序启动入口
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:48
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicConsumerApplication {
/**
* logback
*/
private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicConsumerApplication.class);
/**
* 程序入口
* @param args
*/
public static void main(String[] args)
{
SpringApplication.run(RabbitMqTopicConsumerApplication.class,args);
logger.info("【【【【【Topic队列消息Consumer启动成功】】】】】");
}
}
The manual configuration scan path was explained at the beginning of the article. The main purpose is to scan RabbitMQConfiguration
the information in the configuration class, so that the RabbitAdmin
configuration information can be automatically created to the server
end.
send mail consumer
Send mail messages to monitor register.mail
message queue information, as shown below:
/**
* 发送用户注册成功邮件消费者
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:07
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
@RabbitListener(queues = "register.mail")
public class SendMailConsumer
{
/**
* logback
*/
Logger logger = LoggerFactory.getLogger(SendMailConsumer.class);
/**
* 处理消息
* 发送用户注册成功邮件
* @param userId 用户编号
*/
@RabbitHandler
public void handler(String userId)
{
logger.info("用户:{},注册成功,自动发送注册成功邮件.",userId);
//... 发送注册成功邮件逻辑
}
}
Here I just completed the monitoring of the message, and the specific business logic can be processed according to the requirements.
Create Account Consumer
Create a user account information consumer listening queue register.account
, the code is as follows:
/**
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:04
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
@RabbitListener(queues = "register.account")
public class CreateAccountConsumer {
/**
* logback
*/
Logger logger = LoggerFactory.getLogger(CreateAccountConsumer.class);
/**
* 处理消息
* 创建用户账户
* @param userId 用户编号
*/
@RabbitHandler
public void handler(String userId)
{
logger.info("用户:{},注册成功,自动创建账户信息.",userId);
//... 创建账户逻辑
}
}
Account creation and account initialization logic can be handler
processed in the method. This chapter does not do complex database processing, so there is not too much logic processing in the consumer business.
rabbitmq-topic-provider
Next is the module writing of our message provider. We still create the program entry class first and add the scan configuration @ComponentScan
path. The code is as follows:
/**
* 消息生产者程序启动入口
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:21:48
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@SpringBootApplication
@ComponentScan(value = "com.hengyu.rabbitmq")
public class RabbitMqTopicProviderApplication {
/**
* logback
*/
private static Logger logger = LoggerFactory.getLogger(RabbitMqTopicProviderApplication.class);
/**
* 程序入口
* @param args
*/
public static void main(String[] args)
{
SpringApplication.run(RabbitMqTopicProviderApplication.class,args);
logger.info("【【【【【Topic队列消息Provider启动成功】】】】】");
}
}
Define the message sending interface
Create a QueueMessageService
queue message sending interface and add send
methods as follows:
/**
* 消息队列业务
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:50
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
public interface QueueMessageService
{
/**
* 发送消息到rabbitmq消息队列
* @param message 消息内容
* @param exchangeEnum 交换配置枚举
* @param routingKey 路由key
* @throws Exception
*/
public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception;
}
send
There are three parameters in the method, and the analysis is as follows:
- message: the content of the sent message, which can be of any type, of course, only java.lang.String in this chapter.
- exchangeEnum: Our custom exchange enumeration type, which is convenient for sending messages to the specified exchange.
- routingKey: The content of the routing key when sending a message. This value takes the value in the TopicEnum
enumeration topicRouteKey
as the parameter value.
Let's take a look at the method implementation in the implementation class QueueMessageServiceSupport
of this interface send
, as follows:
/**
* 消息队列业务逻辑实现
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/11/26
* Time:14:52
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Component
public class QueueMessageServiceSupport
implements QueueMessageService
{
/**
* 消息队列模板
*/
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void send(Object message, ExchangeEnum exchangeEnum, String routingKey) throws Exception {
//发送消息到消息队列
rabbitTemplate.convertAndSend(exchangeEnum.getName(),routingKey,message);
}
}
We convert the object type into a string and send it to the message queue server through RabbitTemplate
the method of instance. After receiving the message, we forward the message according to the registered consumers and filter the routing rules, and realize the consumption of the message.convertAndSend
JSON
RabbitMQ
run the test
To facilitate testing we create an UserService
implementation class named as follows:
/**
* 用户业务逻辑
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:10
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@Service
public class UserService
{
/**
* 消息队列发送业务逻辑
*/
@Autowired
private QueueMessageService queueMessageService;
/**
* 随机创建用户
* 随机生成用户uuid编号,发送到消息队列服务端
* @return
* @throws Exception
*/
public String randomCreateUser() throws Exception
{
//用户编号
String userId = UUID.randomUUID().toString();
//发送消息到rabbitmq服务端
queueMessageService.send(userId, ExchangeEnum.USER_REGISTER_TOPIC_EXCHANGE, TopicEnum.USER_REGISTER.getTopicRouteKey());
return userId;
}
}
randomCreateUser
A method named Randomly Create User is added to this class, and the UUID
randomly generated string is passed as the user's number to the user registration message queue to complete the simulated creation of the user.
Write test cases
Next, we create a RabbitMqTester
test class to complete the random user creation message sending, the test case completes a simple UserService
injection, and calls the randomCreateUser
method, as shown below:
/**
* ========================
*
* @author 恒宇少年
* Created with IntelliJ IDEA.
* Date:2017/12/11
* Time:22:10
* 码云:http://git.oschina.net/jnyqy
* ========================
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMqTopicProviderApplication.class)
public class RabbitMqTester
{
/**
* 用户业务逻辑
*/
@Autowired
private UserService userService;
/**
* 模拟随机创建用户 & 发送消息到注册用户消息队列
* @throws Exception
*/
@Test
public void testTopicMessage() throws Exception
{
userService.randomCreateUser();
}
}
So far, our coding is complete, let's start the test by following the steps below:
- Start the
rabbitmq-topic-consumer
message consumer module and check whether the console output is normal- run
rabbitmq-topic-provider
module test case methodtestTopicMessage
- View
rabbitmq-topic-consumer
console output
final effect:
2017-12-30 18:39:16.819 INFO 2781 --- [ main] c.h.r.c.RabbitMqTopicConsumerApplication : 【【【【【Topic队列消息Consumer启动成功】】】】】
2017-12-30 18:39:29.376 INFO 2781 --- [cTaskExecutor-1] c.h.r.consumer.CreateAccountConsumer : 用户:c6ef682d-da2e-4cac-a004-c244ff4c4503,注册成功,自动创建账户信息.
2017-12-30 18:39:29.376 INFO 2781 --- [cTaskExecutor-1] c.h.rabbitmq.consumer.SendMailConsumer : 用户:c6ef682d-da2e-4cac-a004-c244ff4c4503,注册成功,自动发送注册成功邮件.
Summarize
This chapter mainly explains how the TopicExchange
exchange type consumes queue messages, explains the commonly used special characters #
and *
how to match them, and solves the problem that the queue configuration information under multiple modules cannot be automatically created. Another point to note is that TopicExchange
the exchange type does not have a fixed order of message consumption! ! !
The source code of this chapter has been uploaded to the code cloud:
SpringBoot supporting source code address: https://gitee.com/hengboy/spring-boot-chapter
SpringCloud supporting source code address: https://gitee.com/hengboy/spring-cloud-chapter
SpringBoot related For the series of articles, please visit: Catalog: SpringBoot Learning Catalogue
QueryDSL-related series of articles, please visit: QueryDSL General Query Framework Learning Catalogue
SpringDataJPA-related series of articles, please visit: Catalog: SpringDataJPA Learning Catalogue , thank you for reading!
Welcome to join the QQ technical exchange group and make progress together.