经过前面的教程我们知道了 RabbitMQ
里面的基本的队列消息发送,以及不同类型的交换器的消息发送。今天就来分享一下 RabbitMQ
与 Spring
集成的 demo。集成里面主要分享三个类型的消息:
- 默认交换器(也就是队列)类型的消息, 发送一个测试消息到队列
queue.test.queue
里面。然后使用监听去打印获取到的消息。 - fanout(扇形)交换器类型的消息,也就是发布/订阅模型。发送一个订单创建的消息到队列
order.create.queue
里面,然后risk(风控)
、sms(短信)
和statistics(统计)
这三个服务分别去订阅这个服务进行相应的业务处理。 - topic(主题)交换器类型的消息,最灵活的交换器。模拟不同模块发送不同级别的日志到相应主题的队列里面。路由键的模式为<日志级别.模块>。包含所有模块的日志(
all.log.queue
)的队列,所有邮箱模块的日志(email.all.queue
)的队列,邮箱模块错误的日志(email.error.queue
)以及所有模块错误日志(all.error.queue
)的队列这几个不同的主题。
因为是 demo 的形式,把 provider 与 consumer 这两个角色都放在了一个项目里面。整体逻辑为:页面选择一个类型(队列消息, fanout
交换器消息,topic
交换器消息)的消费进行发送,RabbitMqController
对应的方法接收这个消息,然后使用 RabbitTemplate
把消息发送到 RabbitMQ
。配置的 MessageListener
实现者消费对应队列的消费。
1、项目的整体结构
下面就是项目的整体结构图。
2、项目依赖
下面是整个项目的依赖文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.carlzone.spring.rabbitmq</groupId>
<artifactId>spring-rabbitmq</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.7.9.RELEASE</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.0.13</version>
</dependency>
<!-- tools -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3、集成代码
3.1 默认交换器类型消息
QueueService.java
属于普通队列消息消息监听者,用于处理普通队列的消息。
@Component("queueService")
public class QueueService implements MessageListener {
private Logger LOGGER = LoggerFactory.getLogger(QueueService.class);
@Override
public void onMessage(Message message) {
LOGGER.info("QueueService get message : " + new String(message.getBody()));
}
}
3.2 fanout 交换器类型消息
发送订单创建的消息到 RabbitMQ,然后风控服务,短信服务以及监控服务分别监听处理相应逻辑
RiskService.java – 风控
@Component("riskService")
public class RiskService implements MessageListener {
private Logger LOGGER = LoggerFactory.getLogger(RiskService.class);
@Override
public void onMessage(Message message) {
LOGGER.info("RiskService get message : " + new String(message.getBody()));
}
}
SmsService.java – 短信
@Component("smsService")
public class SmsService implements MessageListener {
private Logger LOGGER = LoggerFactory.getLogger(SmsService.class);
@Override
public void onMessage(Message message) {
LOGGER.info("SmsService get message : " + new String(message.getBody()));
}
}
StatisticsService.java – 统计服务
@Component("statisticsService")
public class StatisticsService implements MessageListener {
private Logger LOGGER = LoggerFactory.getLogger(StatisticsService.class);
@Override
public void onMessage(Message message) {
LOGGER.info("StatisticsService get message : " + new String(message.getBody()));
}
}
3.3 topic 交换器类型消息
AllErrorTopicService.java
扫描二维码关注公众号,回复: 8581912 查看本文章
监听所有模块的错误日志级别的消息,然后通过日志的形式打印出来。
@Component("allErrorTopicService")
public class AllErrorTopicService implements MessageListener{
private Logger LOGGER = LoggerFactory.getLogger(AllErrorTopicService.class);
public void onMessage(Message message) {
LOGGER.info("AllErrorTopicService get message : " + new String(message.getBody()));
}
}
AllLogTopicService.java
监听所有模块的所有日志级别的消息,然后通过日志的形式打印出来。
@Component("allLogTopicService")
public class AllLogTopicService implements MessageListener{
private Logger LOGGER = LoggerFactory.getLogger(AllLogTopicService.class);
public void onMessage(Message message) {
LOGGER.info("AllLogTopicService get message : "+new String(message.getBody()));
}
}
EmailAllTopicService.java
监听 Email 模块的所有日志级别的消息,然后通过日志的形式打印出来。
@Component("emailAllTopicService")
public class EmailAllTopicService implements MessageListener{
private Logger LOGGER = LoggerFactory.getLogger(EmailAllTopicService.class);
public void onMessage(Message message) {
LOGGER.info("EmailAllTopicService Get message : " + new String(message.getBody()));
}
}
EmailErrorTopicService.java
监听 Email 模块下的错误日志级别的消息,然后通过日志的形式打印出来。
@Component("emailErrorTopicService")
public class EmailErrorTopicService implements MessageListener{
private Logger LOGGER = LoggerFactory.getLogger(EmailErrorTopicService.class);
public void onMessage(Message message) {
LOGGER.info("EmailErrorTopicService Get message : " + new String(message.getBody()));
}
}
3.4 RabbitMqController.java
RabbitMqController.java
,属于 provider 的逻辑,它的主要作用是发送不同类型的消息到 RabbitMQ 服务器
@RestController
@RequestMapping("rabbit")
public class RabbitMqController {
private Logger logger = LoggerFactory.getLogger(RabbitMqController.class);
@Resource
private RabbitTemplate rabbitTemplate;
@RequestMapping("queue")
public String queue(@RequestParam("message") String message) {
String result;
try {
String str = "queue message,the message is : " + message;
logger.info("send queue message, the message is [" + str + "]");
rabbitTemplate.send("", "queue.test.queue", new Message(str.getBytes(), new MessageProperties()));
result = "success";
} catch (Exception e) {
result = e.getCause().toString();
}
return result;
}
@RequestMapping("fanout")
public String fanout(@RequestParam("message") String message) {
String result;
try {
String str = "fanout message,the message is : " + message;
logger.info("send fanout message, the message is [" + str + "]");
rabbitTemplate.send("fanout-exchange", "", new Message(str.getBytes(), new MessageProperties()));
result = "success";
} catch (Exception e) {
result = e.getCause().toString();
}
return result;
}
@RequestMapping("topic")
public String topic(@RequestParam("message") String message) {
String result;
try {
// 日志严重级别
List<String> severities = Lists.newArrayList("error", "info", "warning");
// 项目模块
List<String> modules = Lists.newArrayList("email", "order", "user");
for (int i = 0; i < severities.size(); i++) {
for (int j = 0; j < modules.size(); j++) {
String routeKey = buildRouteKey(severities.get(i), modules.get(j));
String str = "send topic message, the message is [routeKey :" + routeKey + "][ context : " + message + "]";
rabbitTemplate.send("topic-exchange", routeKey, new Message(str.getBytes(), new MessageProperties()));
}
}
result = "success";
} catch (Exception e) {
result = e.getCause().toString();
}
return result;
}
private String buildRouteKey(String severity, String module) {
StringBuilder routeKey = new StringBuilder();
routeKey.append(severity).append(".").append(module);
return routeKey.toString();
}
}
3.5 index.jsp
index.jsp
, 属于 provider 的代码,用于发送不同消息的页面操作。
<%
String path = request.getContextPath();
System.out.println(path);
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
System.out.println(basePath);
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>RabbitMQ Demo</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<script type="text/javascript" src="<%--<%=basePath%>--%>js/jquery-1.11.0.min.js"></script>
<style type="text/css">
h1 {
margin: 0 auto;
}
#producer {
width: 48%;
border: 1px solid blue;
height: 80%;
align: center;
margin: 0 auto;
}
body {
text-align: center;
}
div {
text-align: center;
}
textarea {
width: 80%;
height: 100px;
border: 1px solid gray;
}
button {
background-color: rgb(62, 156, 66);
border: none;
font-weight: bold;
color: white;
height: 30px;
}
</style>
<script type="text/javascript">
function send(controller) {
if ($("#message").val() == "") {
$("#message").css("border", "1px solid red");
return;
} else {
$("#message").css("border", "1px solid gray");
}
$.ajax({
type: 'post',
url: '<%=basePath%>rabbit/' + controller,
dataType: 'text',
data: {"message": $("#message").val()},
success: function (data) {
if (data == "suc") {
$("#status").html("<font color=green>send success</font>");
setTimeout(clear, 1000);
} else {
$("#status").html("<font color=red>" + data + "</font>");
setTimeout(clear, 5000);
}
},
error: function (data) {
$("#status").html("<font color=red>ERROR:" + data["status"] + "," + data["statusText"] + "</font>");
setTimeout(clear, 5000);
}
});
}
function clear() {
$("#status").html("");
}
</script>
</head>
<body>
<h1>Hello RabbitMQ</h1>
<div id="producer">
<h2>Producer</h2>
<textarea id="message"></textarea>
<br>
<button onclick="send('queue')">Send Queue Message</button>
<button onclick="send('fanout')">Send Fanout Message</button>
<button onclick="send('topic')">Send Topic Message</button>
<br>
<span id="status"></span>
</div>
</body>
</html>
3.6 root-beans.xml
root-beans.xml
这个里面同时配置了 provider 与 consumer 的 rabbitmq 集成逻辑。它们两个的共同配置只有 CachingConnectionFactory , provider 与 consumer 都需要连接 RabbitMQ。而对于 provider 它只需要 RabbitTemplate 进行消息发送,RabbitAdmin 来通过方法来调用 RabbitMQ-UI 提供的 restful 接口来操作 RabbitMQ 的队列、绑定以及里面的属性操作。而对于 consumer 它只需要进行队列名称声明然后把队列与不同的交换机的路由键绑定起来。最后在监听器容器里面把不同的队列与它的监听处理类绑定在一起。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.carlzone.spring.rabbitmq.consumer" />
<!-- provider 与 consumer 共用逻辑 start -->
<!-- rabbitmq 配置 -->
<bean id="rabbitConnectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory" >
<property name="host" value="192.168.75.131" />
<property name="username" value="carl" />
<property name="password" value="198918" />
<property name="port" value="5672" />
<!-- 与服务器的心跳时间(默认60s) -->
<property name="requestedHeartBeat" value="60" />
<!-- RabbitMQ 虚拟主机(默认 "/") -->
<property name="virtualHost" value="/" />
</bean>
<!-- provider 与 consumer 共用逻辑 end -->
<!-- rabbit provider start -->
<!-- rabbitmq admin -->
<rabbit:admin connection-factory="rabbitConnectionFactory" />
<!-- RabbitTemplate 消息模板 -->
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate" >
<constructor-arg ref="rabbitConnectionFactory" />
</bean>
<!-- rabbit provider end -->
<!-- rabbitmq consumer start -->
<!-- 默认交换器 -->
<rabbit:queue name="queue.test.queue" durable="false" />
<!-- fanout交换器 -->
<rabbit:queue name="fanout.queue.sms" durable="false"/>
<rabbit:queue name="fanout.queue.risk" durable="false"/>
<rabbit:queue name="fanout.queue.statistics" durable="false"/>
<!-- 把需要数据的队列与交换器绑定一起 -->
<rabbit:fanout-exchange name="fanout-exchange" durable="false">
<rabbit:bindings>
<rabbit:binding queue="fanout.queue.sms"></rabbit:binding>
<rabbit:binding queue="fanout.queue.risk"></rabbit:binding>
<rabbit:binding queue="fanout.queue.statistics"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- topic交换器 -->
<rabbit:queue name="all.log.queue" durable="false"/>
<rabbit:queue name="email.all.queue" durable="false"/>
<rabbit:queue name="email.error.queue" durable="false"/>
<rabbit:queue name="all.error.queue" durable="false"/>
<!-- 把需要数据的队列通过路由键与交换器绑定一起 -->
<rabbit:topic-exchange name="topic-exchange" durable="false">
<rabbit:bindings>
<rabbit:binding queue="all.log.queue" pattern="#"></rabbit:binding>
<rabbit:binding queue="email.all.queue" pattern="*.email"></rabbit:binding>
<rabbit:binding queue="email.error.queue" pattern="error.email"></rabbit:binding>
<rabbit:binding queue="all.error.queue" pattern="error.*"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- topic交换器 end-->
<!--监听容器-->
<rabbit:listener-container connection-factory="rabbitConnectionFactory">
<!-- queue message -->
<rabbit:listener ref="queueService" queues="queue.test.queue" method="onMessage" />
<!-- fanout exchange message -->
<rabbit:listener ref="riskService" queues="fanout.queue.risk" method="onMessage" />
<rabbit:listener ref="smsService" queues="fanout.queue.sms" method="onMessage" />
<rabbit:listener ref="statisticsService" queues="fanout.queue.statistics" method="onMessage" />
<!-- topic exchange message -->
<rabbit:listener ref="allLogTopicService" queues="all.log.queue" method="onMessage" />
<rabbit:listener ref="emailAllTopicService" queues="email.all.queue" method="onMessage" />
<rabbit:listener ref="emailErrorTopicService" queues="email.error.queue" method="onMessage" />
<rabbit:listener ref="allErrorTopicService" queues="all.error.queue" method="onMessage" />
</rabbit:listener-container>
<!-- rabbit consumer end -->
</beans>
3.7 mvc-beans.xml
mvc-beans.xml
属于 provider 方的逻辑,用于加载 Controller,来接收页面发送过来的消息。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:annotation-driven />
<context:component-scan base-package="cn.carlzone.spring.rabbitmq.provider" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="0" />
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp"></property>
</bean>
</list>
</property>
</bean>
</beans>
3.8 logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d{HH:mm:ss.SSS} [%thread] %level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
<logger name="cn.carlzone.spring.rabbitmq" level="debug" />
<logger name="org.springframework">
<level value="error" />
<additivity value="false" />
</logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
3.9 web.xml
web.xml
加载 Spring 容器,启动项目。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:root-beans.xml</param-value>
</context-param>
<filter>
<filter-name>characterEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring MVC Config Start -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-beans.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
4、验证
启动项目,输入 http://localhost:8080/
会出现以下页面。
4.1 发送队列消息
发送一条队列消息 queue message
,可以看到在控制台打印了 Controller 发送了一条消息,然后消费了一条队列消息。
4.2 发送 fanout 消息
发送一条 fanout 交换器类型的消息,可以统计、短信以及风控都消费了这条消息。
4.3 发送 topic 消息
发送一条 topic 交换器类型的消息,后台对应三个模块以及三个日志级别可以看到 AllLogTopicService
消费了所有的日志一共消费了 9 条消息。AllErrorTopicService
消费了所有的 error
级别的日志一共消费了 3 条消息。EmailAllTopicService
消费了所有的邮件类的消息,一共消费了 3 条。EmailErrorTopicService
消费邮件服务的错误日志级别的消息只消费了一条消息。