前言:这是中间件一个系列的文章之一,有需要的朋友可以看看这个系列的其他文章:
消息中间件系列一、消息中间件的基本了解
消息中间件系列二、Windows下的activeMQ和rabbitMQ的安装
消息中间件系列三、JMS和activeMQ的简单使用
消息中间件系列四、认识AMQP和RabbiyMq的简单使用
消息中间件系列五、rabbit消息的确认机制
消息中间件系列六,rabbit与spring集成实战
本项目是rabbit和spring整合的实战学习项目,模拟电商下单和库存管理的过程,看过前面几篇博客的同学,相信这篇博客对你不会再难了。一些和本章学习不太相关的内容不会做过多说明,需要的朋友可以下载源码自己查看运行:rabbit与spring集成实战源码。
生产者订单系统
一、pom文件引入相关包
rabbit和spring整合以下两个包是必须的
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
其他包根据项目需要引进;引入的其他包有兴趣查看的可以下载源码查看。
二、配置文件
web.xml和spring-mvc.xml不是博客重点,不再贴出,有兴趣的下载源码查看;下面说明applicationContext.xml的内容:
1、配置文件中增加命名空间:
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd
1、连接工厂配置
<!-- rabbitMQ配置 -->
<bean id="rabbitConnectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="127.0.0.1"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
<property name="channelCacheSize" value="8"/>
<property name="port" value="5672"></property>
<!-- 发布确认必须配置在CachingConnectionFactory上 -->
<property name="publisherConfirms" value="true"/>
</bean>
2、<rabbit:admin>
配置<rabbit:admin>
之后才能根据配置文件去生产队列交换器等信息。
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
4、声明队列
durable:是否持久化
<rabbit:queue name="depot_queue" durable="true"/>
5、声明交换器
name:交换器名称,durable:是否持久化
<rabbit:direct-exchange name="depot-amount-exchange"
xmlns="http://www.springframework.org/schema/rabbit" durable="true">
<rabbit:bindings>
<rabbit:binding queue="depot_queue" key="amount.depot" ></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
6、队列和交换器进行绑定
queue:队列名称,key:绑定的路由键,需要在交换器中绑定。
<rabbit:bindings>
<rabbit:binding queue="depot_queue" key="amount.depot" ></rabbit:binding>
</rabbit:bindings>
7、生产者端要声明RabbitmqTemplate
<!-- 创建rabbitTemplate 消息模板类 -->
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg ref="rabbitConnectionFactory"></constructor-arg>
<!--消息确认回调 -->
<property name="confirmCallback" ref="confirmCallback"/>
<property name="returnCallback" ref="sendReturnCallback"/>
</bean>
完整的applicationContext.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 查找最新的schemaLocation 访问 http://www.springframework.org/schema/ -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
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/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd">
<!-- 配置扫描路径 -->
<context:component-scan base-package="com.dongnaoedu">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- rabbitMQ配置 -->
<bean id="rabbitConnectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="127.0.0.1"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
<property name="channelCacheSize" value="8"/>
<property name="port" value="5672"></property>
<!-- 发布确认必须配置在CachingConnectionFactory上 -->
<property name="publisherConfirms" value="true"/>
</bean>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:queue name="depot_queue" durable="true"/>
<rabbit:direct-exchange name="depot-amount-exchange"
xmlns="http://www.springframework.org/schema/rabbit" durable="true">
<rabbit:bindings>
<rabbit:binding queue="depot_queue" key="amount.depot" ></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 创建rabbitTemplate 消息模板类 -->
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg ref="rabbitConnectionFactory"></constructor-arg>
<!--消息确认回调 -->
<property name="confirmCallback" ref="confirmCallback"/>
</bean>
</beans>
三、其他代码
1、controller
package com.dongnaoedu.controller;
import com.dongnaoedu.service.ProcessOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class OrderController {
private Logger logger = LoggerFactory.getLogger(OrderController.class);
private static final String SUCCESS = "suc";
private static final String FAILUER = "failure";
@Autowired
private ProcessOrder processOrder;
@RequestMapping("/order")
public String userReg(){
return "index";
}
@RequestMapping("/confirmOrder")
@ResponseBody
public String confirmOrder(@RequestParam("goodsId")String goodsId,
@RequestParam("amount")int amount){
try {
processOrder.processOrder(goodsId,amount);
return SUCCESS;
} catch (Exception e) {
logger.error("订单确认异常!",e);
return FAILUER;
}
}
}
2、ProcessOrder
package com.dongnaoedu.service;
import com.dongnaoedu.rpc.DepotService;
import com.dongnaoedu.rpc.RpcProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import sun.security.x509.IPAddressName;
import java.net.InetSocketAddress;
@Service
public class ProcessOrder {
private Logger logger = LoggerFactory.getLogger(ProcessOrder.class);
@Autowired
@Qualifier("mq")
private IProDepot proDepot;
public void processOrder(String goodsId,int amount){
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("--------------------["+goodsId+"]订单入库完成,准备变动库存!");
proDepot.processDepot(goodsId,amount);
}
}
3、MqMode
生产者可通过rabbitTemplate调用send方法发送消息,参数分别为exchange交换器,routingKey路由键,Message对象。
package com.dongnaoedu.service;
import com.dongnaoedu.vo.GoodTransferVo;
import com.google.gson.Gson;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
@Qualifier("mq")
public class MqMode implements IProDepot {
private final static String DEPOT_RK = "amount.depot";
private final static String DEPOT_EXCHANGE = "depot-amount-exchange";
@Autowired
RabbitTemplate rabbitTemplate;
private static Gson gson = new Gson();
public void processDepot(String goodsId, int amount) {
GoodTransferVo goodTransferVo = new GoodTransferVo();
goodTransferVo.setGoodsId(goodsId);
goodTransferVo.setChangeAmount(amount);
goodTransferVo.setInOrOut(false);
String goods = gson.toJson(goodTransferVo);
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);//设置消息属性以便进行消息持久化,投递模式设置为2,
rabbitTemplate.send(DEPOT_EXCHANGE, DEPOT_RK,new Message(goods.getBytes(), messageProperties));
}
}
4、ConfirmCallback
消息的确认回调,必须实现RabbitTemplate.ConfirmCallback接口
package com.dongnaoedu.service.callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.stereotype.Service;
@Service
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback {
private Logger logger = LoggerFactory.getLogger(ConfirmCallback.class);
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
logger.info("消息确认发送给mq成功");
} else {
//处理失败的消息
logger.info("消息发送给mq失败,考虑重发:"+cause);
}
}
}
消费者库存系统
一、配置文件
其他配置文件可下载源码查看。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 查找最新的schemaLocation 访问 http://www.springframework.org/schema/ -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
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/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd">
<!-- 配置扫描路径 -->
<context:component-scan base-package="com.dongnaoedu">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- rabbitMQ配置 -->
<bean id="rabbitConnectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="127.0.0.1"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
<property name="channelCacheSize" value="8"/>
<property name="port" value="5672"></property>
</bean>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:queue name="depot_queue" durable="true"/>
<rabbit:direct-exchange name="depot-amount-exchange"
xmlns="http://www.springframework.org/schema/rabbit" durable="true">
<rabbit:bindings>
<rabbit:binding queue="depot_queue" key="amount.depot" ></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 对消息要手动确认 -->
<rabbit:listener-container connection-factory="rabbitConnectionFactory"
acknowledge="manual">
<rabbit:listener queues="depot_queue" ref="processDepot"
method="onMessage" />
</rabbit:listener-container>
</beans>
二、其他源码
1、ProcessDepot
package com.dongnaoedu.mq;
import com.dongnaoedu.service.DepotManager;
import com.dongnaoedu.vo.GoodTransferVo;
import com.google.gson.Gson;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class ProcessDepot implements ChannelAwareMessageListener {
private static Logger logger = LoggerFactory.getLogger(ProcessDepot.class);
@Autowired
private DepotManager depotManager;
private static Gson gson = new Gson();
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
String msg = new String(message.getBody());
logger.info(">>>>>>>>>>>>>>接收到消息:"+msg);
GoodTransferVo goodTransferVo = gson.fromJson(msg,GoodTransferVo.class);
try {
depotManager.operDepot(goodTransferVo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),
false);
logger.info(">>>>>>>>>>>>>>库存处理完成,应答Mq服务");
} catch (Exception e) {
logger.error(e.getMessage());
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);//deliveryTag投递的标记符,multiple是否进行批量回复,requeue是否重新入队分发消息
logger.info(">>>>>>>>>>>>>>库存处理失败,拒绝消息,要求Mq重新派发");
throw e;
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
}
2、DepotManager
package com.dongnaoedu.service;
import com.dongnaoedu.vo.GoodTransferVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DepotManager {
@Autowired
private Depot depot;
public void operDepot(GoodTransferVo goodTransferVo){
if(goodTransferVo.isInOrOut()){
depot.inDepot(goodTransferVo.getGoodsId(),goodTransferVo.getChangeAmount());
}else{
depot.outDepot(goodTransferVo.getGoodsId(),goodTransferVo.getChangeAmount());
}
}
}
3、Depot
package com.dongnaoedu.service;
import com.dongnaoedu.rpc.DepotService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
@Service
public class Depot {
private static Logger logger = LoggerFactory.getLogger(Depot.class);
private ConcurrentHashMap<String,Integer> goodsData =
new ConcurrentHashMap<String, Integer>();
@PostConstruct
public void initDepot(){
goodsData.put("001",1000);
goodsData.put("002",500);
goodsData.put("003",600);
goodsData.put("004",700);
}
/*使用jdk1.8以下的用这个方法
public synchronized void inDepot(String goodsId,int addAmout){
int amount = goodsData.get(goodsId)+addAmout;
goodsData.put(goodsId,amount);
}
*/
//增加库存
public void inDepot(String goodsId,int addAmout){
logger.info("+++++++++++++++++增加商品:"+goodsId+"库存,数量为:"+addAmout);
int newValue = goodsData.compute(goodsId, new BiFunction<String, Integer, Integer>() {
public Integer apply(String s, Integer integer) {
return integer == null ? addAmout : integer + addAmout;
}
});
logger.info("+++++++++++++++++商品:"+goodsId+"库存,数量变为:"+newValue);
}
/*使用jdk1.8以下的用这个方法
public synchronized void outDepot(String goodsId,int reduceAmout){
int amount = goodsData.get(goodsId)-reduceAmout;
goodsData.put(goodsId,amount);
}
*/
//减少库存
public void outDepot(String goodsId,int reduceAmout){
logger.info("-------------------减少商品:"+goodsId+"库存,数量为:"+reduceAmout);
int newValue = goodsData.compute(goodsId, new BiFunction<String, Integer, Integer>() {
public Integer apply(String s, Integer integer) {
return integer == null ? 0 : integer - reduceAmout;
}
});
logger.info("-------------------商品:"+goodsId+"库存,数量变为:"+newValue);
}
public int getGoodsAmount(String goodsId){
return goodsData.get(goodsId);
}
}
补充:源码的rpc调用部分补在这里解释,有兴趣的朋友可以自行研究。源码链接:rabbit与spring集成实战源码。
目前rabbit的内容就先写到这里,rabbit集群和rpc的内容以后有时间在补充,接下来我会更新缓存系列,敬请期待。