基本概念
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這個消息開始處理。