Spring+MVC整合Rabbitmq的使用

项目中有需要异步的业务,所以用到了Rabbitmq。

mq结构参考

目前项目有前端、后台以及对外的api,考虑到这三个子项目以后会用到mq,所以我将mq的结构抽离出来:发送端+发送端的dubbo+zookeeper接口、消费端,在需要异步请求的代码中,引入发送端的接口就可以了;然后消费端进行处理消息。在业务代码中无需引用mq的jar包或配置,减少耦合的问题。

1,Linux上Rabbitmq的安装

  • 下载Rabbitmq,查看你的linux环境版本(命令:cat /etc/issue)。我的是CentOS6,所以下载的是CentOS6.x这个
  • 下载erlang,erlang是Rabbitmq的编译语言,需要配对使用。rabbitmq与erlang以下
  • 上传到linux然后安装:
yum install -y erlang-19.0.4-1.el6.x86_64.rpm
yum install -y rabbitmq-server-3.5.6-1.noarch.rpm
  • 启动rabbitmq即可
service rabbitmq-server start

2,Rabbimq的管理

  • 创建用户
#创建用户
rabbitmqctl add_user 用户名 用户密码

#分配用户角色:超级管理员(rabbitmq的角色包括none、
management、policymaker、monitoring、administrator)
rabbitmqctl set_user_tags root administrator

#赋予Virtual host权限,下面的/是默认的Virtual host
rabbitmqctl set_permissions -p / root "." "." ".*"
  • 登录可视化界面,地址是http://linux地址:15672,输入刚刚创建的用户名及密码即可。如果访问不了查看防火墙是否关闭或对15672这个端口是否开放。在java客户端的默认端口是5672,也要开放。

  • 添加一个Virtual host,每个Virtual host是相互隔离的,可以理解成一个独立的mq服务器。
rabbitmqctl add_vhost 虚拟名称

也可以在管理界面添加。包括交换机(exchange)、队列(queue)、绑定(binding)都可以在管理界面添加,不过这个一般是在代码中配置的

  • 赋予用户于Virtual host的权限
rabbitmqctl set_permissions -p 虚拟名称 root "." "." ".*"

3,Spring+MVC与Rabbitmq的整合:发送端

  • 消息发送端配置文件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"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans      
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
                        http://www.springframework.org/schema/context      
                        http://www.springframework.org/schema/context/spring-context-3.0.xsd   
                        http://www.springframework.org/schema/rabbit    
                        http://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd
                        http://www.springframework.org/schema/util  
    					http://www.springframework.org/schema/util/spring-util-3.0.xsd">

	<!-- 创建连接工厂 -->
	<rabbit:connection-factory
		id="connectionFactory" virtual-host="${rabbit.virtual-host}"
		username="${rabbit.username}" password="${rabbit.password}"
		host="${rabbit.host}" port="${rabbit.port}" />
		
	<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory" ignore-declaration-exceptions="true"/>
	
	<!-- 替换JACKSON为FASTJSON -->
	<!-- <bean id="messageConverter" class="自定义的转换器"></bean> -->
	
	<!-- spring template声明 -->
	<!-- <rabbit:template id="amqpTemplate" connection-factory="connectionFactory" message-converter="messageConverter"/> -->
	<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
		
</beans>

 

连接信息rabbimq.properties

rabbit.host=linux地址
rabbit.username=root
rabbit.password=root
rabbit.port=5672
rabbit.virtual-host=root_host

然后在你的spring配置文件引入这两个文件

配置文件中的消息转换工具,就是将消息序列化,可以用默认的Jackson,也可以用其他的转换工具,这里用的是Fastjson

import java.io.UnsupportedEncodingException;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.AbstractMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;

import net.sf.ezmorph.object.DateMorpher;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;

/**
 * rabbitMQ 转换器 fastJson
 */
public class FastJsonMessageConverter extends AbstractMessageConverter {

	public static final String DEFAULT_CHARSET = "UTF-8";
	private volatile String defaultCharset = DEFAULT_CHARSET;

	public FastJsonMessageConverter() {
		super();
	}

	public void setDefaultCharset(String defaultCharset) {
		this.defaultCharset = (defaultCharset != null) ? defaultCharset : DEFAULT_CHARSET;
	}

	public Object fromMessage(Message message) throws MessageConversionException {
		return null;
	}

	@SuppressWarnings("unchecked")
	public <T> T fromMessage(Message message, T t) {
		String json = "";
		try {
			json = new String(message.getBody(), defaultCharset);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		JSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(
				new String[] { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss" }));
		return (T) JSONObject.toBean((JSONObject.fromObject(json)), t.getClass());
	}

	protected Message createMessage(Object objectToConvert, MessageProperties messageProperties)
			throws MessageConversionException {
		byte[] bytes = null;
		try {
			String jsonString = com.alibaba.fastjson.JSONObject.toJSONString(objectToConvert);
			bytes = jsonString.getBytes(this.defaultCharset);
		} catch (UnsupportedEncodingException e) {
			throw new MessageConversionException("Failed to convert Message content", e);
		}
		messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
		messageProperties.setContentEncoding(this.defaultCharset);
		if (bytes != null) {
			messageProperties.setContentLength(bytes.length);
		}
		return new Message(bytes, messageProperties);

	}
}
  • 配置类,交换机与队列的绑定。交换机与队列类似一个多对多的关系

首先写一个枚举

public class RabbitInfo {
	/** 交换机名称 */
	public final static String USER_EXCHANGE = "user_exchange";
	/** 绑定名称 */
	public final static String USER_DELETE_BINDING = "user_delete_binding";
	/** 队列名称 */
	public final static String USER_DELETE_QUEUE = "user_delete_queue";

	public enum RabbitEnum {

		ITEMEDIT(USER_EXCHANGE, USER_DELETE_BINDING, USER_DELETE_QUEUE);

		private String exchange;
		private String binding;
		private String queue;

		private RabbitEnum(String exchange, String binding, String queue) {
			this.exchange = exchange;
			this.binding = binding;
			this.queue = queue;
		}

		public String getExchange() {
			return exchange;
		}

		public String getBinding() {
			return binding;
		}

		public final String getQueue() {
			return queue;
		}
	}
}

 然后初始化枚举里面定义的交换机、绑定、以及队列

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import common.rabbitmq.RabbitInfo;
import common.rabbitmq.RabbitInfo.RabbitEnum;

/**
 * 交换机与队列绑定
 * 
 * @author awarmbody
 *
 */

@Configuration
public class DirectRabbitConfig {

	@Autowired
	private AmqpAdmin amqpAdmin;
	
	public static final Logger LOGGER = LoggerFactory.getLogger(DirectRabbitConfig.class);

	@PostConstruct
	public void init() {
		RabbitEnum[] rabbitEnums = RabbitInfo.RabbitEnum.values();
		Map<String,DirectExchange> exchanges = new HashMap<String,DirectExchange>();
		Map<String,Queue> queues = new HashMap<String,Queue>();
		Map<String,Binding> bindings = new HashMap<String,Binding>();
		for (RabbitEnum rabbitEnum : rabbitEnums) {
			DirectExchange exchange;
			Queue queue;
			if(exchanges.containsKey(rabbitEnum.getExchange())) {
				exchange = exchanges.get(rabbitEnum.getExchange());
			}else {
				exchange = (DirectExchange) ExchangeBuilder.directExchange(rabbitEnum.getExchange()).durable(true).build();
				exchanges.put(rabbitEnum.getExchange(),exchange);
			}
			if(queues.containsKey(rabbitEnum.getQueue())) {
				queue = queues.get(rabbitEnum.getQueue());
			}else {
				queue = QueueBuilder.durable(rabbitEnum.getQueue()).build();
				queues.put(rabbitEnum.getQueue(), queue);
			}
			if(bindings.containsKey(rabbitEnum.getBinding())) {
				LOGGER.warn("There are bindings with the same name:exchange-name=" + rabbitEnum.getExchange()
						+ ",queue-name=" + rabbitEnum.getQueue() + ",binding-key=" + rabbitEnum.getBinding());
				continue;
			}
			amqpAdmin.declareExchange(exchange);
			amqpAdmin.declareQueue(queue);
			Binding binding = BindingBuilder.bind(queue).to(exchange).with(rabbitEnum.getBinding());
			amqpAdmin.declareBinding(binding);
			bindings.put(rabbitEnum.getBinding(), binding);
		}
		exchanges = null;
		bindings = null;
		queues = null;
	}
}

 消息发送端的配置就到这里了。

  • 发送消息

 写一个用户的接口,定义一个删除用户的方法,然后将接口的实现类通过dubbo注解发布发zookeeper,在业务代码里注入UserService就可以,业务代码没有任何关于mq的信息。这是多态和反射的一些概念这里就不陈述了

public interface UserService {
	
	void deleteUser(String exchangeName, String bindingKey, Long userId);
}
@Service
public class UserServiceImpl implements UserService {
	
	@Autowired
	private AmqpTemplate amqpTemplate;
	
	@Override
	public void deleteUser(String exchangeName, String bindingKey, Long userId) {
		amqpTemplate.convertAndSend(exchangeName,bindingKey, goodId);
	}
}

4,Spring+MVC与Rabbitmq的整合:消费端

  • 消息消费端配置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"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans      
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
                        http://www.springframework.org/schema/context      
                        http://www.springframework.org/schema/context/spring-context-3.0.xsd   
                        http://www.springframework.org/schema/rabbit    
                        http://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd
                        http://www.springframework.org/schema/util  
    					http://www.springframework.org/schema/util/spring-util-3.0.xsd">

	<!-- 创建连接工厂 -->
	<rabbit:connection-factory
		id="connectionFactory" virtual-host="${rabbit.virtual-host}"
		username="${rabbit.username}" password="${rabbit.password}"
		host="${rabbit.host}" port="${rabbit.port}" />
	<rabbit:admin connection-factory="connectionFactory" />

	<!-- 替换JACKSON为FASTJSON -->
	<!-- <bean id="messageConverter"  class="自定义的转换器"></bean> -->

	<!-- 开启rabbitMQ注解 -->
	<rabbit:annotation-driven />
	
	<!-- 消息监听 -->
	<bean id="rabbitListenerContainerFactory"
	    class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
	    <!-- <property name="messageConverter" ref="messageConverter" /> -->
	    <property name="connectionFactory" ref="connectionFactory" />
	    <property name="concurrentConsumers" value="3" />
	    <property name="maxConcurrentConsumers" value="10" />
	</bean>
</beans>

连接信息rabbimq.properties

rabbit.host=linux地址
rabbit.username=root
rabbit.password=root
rabbit.port=5672
rabbit.virtual-host=root_host

然后在你的spring配置文件引入这两个文件

 <!--引入配置属性文件 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:/config/mq/rabbitmq.properties</value>
            </list>
        </property>
    </bean>
 <!--引入rabbitmq配置文件 -->
    <import resource="spring-rabbitmq.xml" />

 

  •  接收消息
@Component
public class UserCustomer {
	
	@Autowired
	private UserService userService;
	
	/**
	 * 刪除用户
	 * @param userId
	 */
	@RabbitListener(queues = RabbitInfo.USER_DELETE_QUEUE)
	public void deleteUser(Long userId) {
		userService.deleteUser(userId);
	}
}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

最后 

当业务数据量较大的时候,就要考虑线程的事务(推荐用redis缓存一个变量作为一个锁,快捷高效)、消息发送失败或消费失败的处理(可基于消息确认机制处理,Rabbitmq有提供)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值