基本概念
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这个消息开始处理。