7、Spring Integration RabbitMQ

经过前面的教程我们知道了 RabbitMQ 里面的基本的队列消息发送,以及不同类型的交换器的消息发送。今天就来分享一下 RabbitMQSpring 集成的 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

监听所有模块的错误日志级别的消息,然后通过日志的形式打印出来。

@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 消费邮件服务的错误日志级别的消息只消费了一条消息。

这里写图片描述

发布了173 篇原创文章 · 获赞 221 · 访问量 70万+

猜你喜欢

转载自blog.csdn.net/u012410733/article/details/81676766
今日推荐