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這個消息開始處理。
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章