Rabbit MQ - tutorial 7, Spring RabbitMQ

基本概念

template(RabbitTemplate ):用于发送和接收消息的高级抽象(见下面rabbitmq.xml中的配置)
Spring AMQP提供对消息驱动的POJO的支持,促进使用依赖注入和声明式配置

tutorial 7 Spring RabbitMQ

本例用一个简单的spring mvc maven项目来演示。

(1) 项目依赖

新建maven web app项目,POM.xml中添加如下依赖。

<dependencies>
        <!-- spring 5 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- spring MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- rabbit mq -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>${rabbitmq.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>${springrabbitmq.version}</version>
        </dependency>
    </dependencies>

(2) applicationContext.xml & spring-mvc.xml

添加spring配置文件

//applicationContext.xml

<?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: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/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath*:spring-rabbitmq.xml" />
    <context:component-scan base-package="hpp.example.producer,hpp.example.consumer" />

</beans>

注意:上面配置里面对应的文件,后面会说到,这里,你先可以加空文件,或者把上面注释掉,等文件加了之后再恢复回来。

//spring-mvc.xml

<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

    <!-- 定义扫描的包,用以加载对应的控制器和其它一些组件 -->
    <context:component-scan base-package="hpp.example.controller"/>

    <!-- 定义视图解析器,解析器中定义了前缀和后缀,这样视图就知道去Web工程的/WEB-INF/jsp文件夹中找到对应的jsp文件作为视图响应用户请求-->
    <!-- 找Web工程/WEB-INF/jsp文件夹,且文件结尾为jsp的文件作为映射 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>


    <!-- 如果有配置数据库事务,需要开启注解事务的,需要开启这段代码 -->
    <!--<tx:annotation-driven transaction-producer="transactionManager"/>-->
</beans>

(3) 配置web.xml 并确认是否配置成功

//web.xml 

<!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>
  <display-name>Archetype Created Web Application</display-name>
  <!-- 配置Spring IoC配置文件的路径,其默认值为/WEB-INF/applicationContext.xml -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!-- 配置ContextLoaderListener用以初始化Spring IoC -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 配置DispatcherServlet -->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <!-- 大于等于0时,在服务器启动时就初始化;否则在第一次调用时初始化;值越小优先级越高 -->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- Servlet拦截配置 -->
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

添加一个uri来验证spring mvc是否配置成功:

//MyController.java

@Controller("myController") //表示这是一个控制器
//指定对应请求的URI. Spring MVC在初始化时会将这些信息解析并保存成HandlerMapping,
//当发生请求时,Spring MVC就会使用这些信息去找对应的controller提供服务
@RequestMapping("/my")
public class MyController {

    @ResponseBody
    @RequestMapping(value="test", method = RequestMethod.GET, produces = "text/plain; charset=utf-8")
    public String ExampleSend(){
        return "spring mvc is ready";
    }
}

运行之后,浏览器中输入:http://localhost:8080/my/test,看到spring mvc is ready,表示配置正确。

(4) 添加spring-rabbitmq.xml

//rabbitmq.properties

rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.vhost=/
//spring-rabbitmq.xml

<?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:property-placeholder location="classpath:rabbitmq.properties"/>
    <rabbit:connection-factory id="connectionFactory"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               virtual-host="${rabbitmq.vhost}"/>

    <rabbit:admin connection-factory="connectionFactory"/>

    <!--
    exclusive:排他,该队列仅对首次声明它的连接可见,并在连接断开时自动删除
    Auto-delete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
    Durable:持久化
    -->
    <!-- 声明queue -->
    <rabbit:queue id="queue1" name="queue1" durable="true" auto-delete="false" exclusive="false"/>
    <!-- 定义exchange并绑定到queue -->
    <rabbit:direct-exchange name="exchange1" id="exchange1" durable="true" auto-delete="false">
        <rabbit:bindings>
            <rabbit:binding queue="queue1" key="queue1_key"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--RabbitMQ的Consumer, 配置为手动返回ack确认-->
    <!--
    acknowledge:自动ack,还是手动ack,本例设置为手动ack
    concurrency: 消息监听者的并发数
    prefetch:预取值,默认为250(最早的版本默认是1);如果需要严格的消息排序,预取值应该被设置成1;如果希望消息均匀的分派到每个消费者中,这个值也应该设为1;
    -->
    <bean id="messageReceiver1" class="hpp.example.consumer.MyConsumer1"></bean>
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" concurrency="1" prefetch="3">
        <rabbit:listener queues="queue1" ref="messageReceiver1"/>
    </rabbit:listener-container>

    <!-- RabbitMQ的Producer, 定义rabbit template用于数据的发送 -->
    <rabbit:template id="amqpTemplate1" connection-factory="connectionFactory" exchange="exchange1" routing-key="queue1_key"/>

</beans>

(4) Producer实现

//MyProducer.java

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class MyProducer {
    @Resource
    private RabbitTemplate amqpTemplate1;

    public void sendMessage(Object message) {
        System.out.println("发送:" + message);
        amqpTemplate1.convertAndSend(message);
    }
}

(5) Consumer实现

//MyConsumer1.java

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;

import static java.lang.Thread.sleep;


public class MyConsumer1 implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        try {
            String msg = String.format("接收:queue=%s, exchange=%s, routingKey=%s, message=%s",
                    message.getMessageProperties().getConsumerQueue(),
                    message.getMessageProperties().getReceivedExchange(),
                    message.getMessageProperties().getReceivedRoutingKey(),
                    new String(message.getBody()));
            System.out.println(msg);

            //消息处理,用sleep模拟
            sleep(2000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }finally {//消息处理完成后,手动返回ack确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
}

(6) Controller里面添加uri用于发送消息


import hpp.example.producer.MyProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Random;

@Controller("myController") 
@RequestMapping("/my")
public class MyController {

    @Autowired
    MyProducer myProducer;

    @ResponseBody
    @RequestMapping(value="send", method = RequestMethod.GET, produces = "text/plain; charset=utf-8")
    public String ExampleSend(@RequestParam(value = "num") int num ){

        if(num < 1) {
            myProducer.sendMessage(generateMessage(1));
        }
        else{
            for(int i=1; i <= num; ++i){
                myProducer.sendMessage(generateMessage(i));
            }
        }
        //返回执行结果
        return String.format("call send message %d times", num < 1 ? 1 : num);
    }

    private String generateMessage(int val){
        Random rdm = new Random();
        int num = rdm.nextInt(100) + 1;//[1, 100]
        String msg = String.format("[%d].%d", val, num);
        return msg;
    }
}

(7) 运行结果

这里写图片描述

大家可以试试:
发100个消息(http://localhost:8080/my/send?num=100),
(1)当它还没有处理完所有消息(比如到第10个)直接把程序停掉,过一会儿,重新跑起来,看看consumer是否还能继续接收和处理消息?
答案是“可以继续处理的”。
(2)当它还没有处理完所有消息时,把rabbitmq服务重启一下,看是否还能继续接收处理消息?
答案也是可以继续的。我这里发送了1000个message,在第618个消息时停止的rabbitMQ server,然后重新启动rabbitMQ server,还是会继续从618这个消息开始处理。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/hetoby/article/details/79878987