Spring接入RabbitMQ

  在原有的Spring项目中接入RabbitMQ,使用的Spring集成RabbitMQ,这样 RabbitMQ的功能调用通过Spring封装,调用更加简洁。

  首先pom 文件中引入依赖

  

<dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>${spring-rabbit-version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-messaging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

因为已有项目中Spring的版本与引入的spring-rabbit包中依赖的版本不一致,故exclusions中设置了多条 exclusion。

引入 jar 包后,开始 RabbitMQ 的配置,思路等同于数据库,要将程序与 RabbitMQ建立连接,以配置的形式。官网给出的 demo中,RabbitMQ接入 Spring 以xml 文件的形式;接入 SpringBoot以注解的形式。以下以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"
    xsi:schemaLocation="http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 连接服务配置 -->
    <!-- 消息未达到exchange, confirm callback and ack = false ; 消息达到 exchange, confirm callback and ack = true -->
    <!-- 消息入 queue成功,no return callback ; 消息入 queue 失败,return callback     -->
    <rabbit:connection-factory id="mqConnectionFactory" host="${rabbitMQ.host}" 
    username="${rabbitMQ.username}" password="${rabbitMQ.password}" port="${rabbitMQ.port}" 
    publisher-confirms="true" publisher-returns="true"/>
    
    <rabbit:admin connection-factory="mqConnectionFactory"/>
    
    <!-- 配置可用 rabbit 注解 -->
    <!-- <rabbit:annotation-driven/>
    <bean id="rabbitListenerContainerFactory"
          class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
        <property name="connectionFactory" ref="mqConnectionFactory"/>
        <property name="concurrentConsumers" value="3"/>
        <property name="maxConcurrentConsumers" value="10"/>
    </bean> -->
        
    <rabbit:listener-container connection-factory="mqConnectionFactory" acknowledge="manual">
        <rabbit:listener queue-names="q.crm.msg" ref="msgSenderConsumerServiceImpl"/>  
    </rabbit:listener-container>
    
    <!-- exchange - rabbitTemplate 配置 多个名称不同,注入使用 @Qualifier  mandatory set true, return callback can success -->
    <rabbit:template id="rabbitTemplate" connection-factory="mqConnectionFactory"
        confirm-callback="confirmCallBackListener" return-callback="returnCallBackListener" mandatory="true"/> 
</beans>

将这个配置文件加载到 spring 的配置文件中,文件中的 host等通过 properties 文件设置。

生产者,消费者部分:

生产者:在需要使用的类中注入

@Autowired
private RabbitTemplate rabbitTemplate;

生产部分

rabbitTemplate.convertAndSend("e.crm", "k.crm.push.msg", JSON.toJSONString(msg));

第一个参数(e.crm)指定 exchange,第二个参数(k.crm.push.msg)指定binding key,第三个参数为消息内容。

消费部分

@Service("msgSenderConsumerServiceImpl")
public class MsgSenderConsumerServiceImpl implements ChannelAwareMessageListener {
    
    public void onMessage(Message message, Channel channel) throws IOException {
        String taskStr = new String(message.getBody(), "UTF-8");
    }

实现的是ChannelAwareMessageListener接口,为在方法中使用Channel,不需要使用的话可直接实现MessageListener接口。

配置中生产者确认机制在配置中需要的类有:

@Service("confirmCallBackListener")
public class ConfirmCallBackListener implements ConfirmCallback{
    
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //exchange未到达成功 ack=false
        //处理逻辑
    }

}
@Service("returnCallBackListener")
public class ReturnCallBackListener implements ReturnCallback{
    
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        //入队列未成功,则调用此方法
        //处理逻辑
    }

}

通过以上配置,RabbitMQ 接入完成。

但是哩,每个 queue的接入都需要修改配置文件,虽然解耦的方式,但是queue 接入后业务比较固定,修改配置文件就不如注解方式灵活。故接入将消费者修改成注解的方式。

放开 RabbitMQ配置中可使用注解的配置,这样在类中可直接使用 RabbitMQ相关的注解。

@Service("msgSenderConsumerServiceImpl")
@RabbitListener(queues = "q.crm.msg")
public class MsgSenderConsumerServiceImpl {

    @RabbitHandler
    public void onMessage(@Payload String message) throws IOException {
        System.out.println(message);
    }
    
}

这种形式则 RabbitMQ配置文件中的这部分就不需要了

<rabbit:listener-container connection-factory="mqConnectionFactory">
        <rabbit:listener queue-names="q.crm.msg" ref="msgSenderConsumerServiceImpl"/>  
    </rabbit:listener-container> 

好,接下来,我们的业务想要在消费者端手动确认,

消费端通过 xml配置时比较好设置,如下:

<rabbit:listener-container connection-factory="mqConnectionFactory" acknowledge="manual">
        <rabbit:listener queue-names="q.crm.msg" ref="msgSenderConsumerServiceImpl"/>  
    </rabbit:listener-container> 

但是哩,我们想要用注解的形式,从网上搜了各种资料,一直没找到解决方案。

注解使用@RabbitListener时,需要在配置文件中指定rabbitListenerContainerFactory,但是手动确认的设置是在rabbitListenerContainer中,这样就找不到设置的入口。

网上搜索的很多资料,通过设置:spring.rabbitmq.listener.simple.acknowledge-mode=manual

我将设置添加到 spring 的配置文件中,也是不好使,这个配置应该是 Spring-Boot 项目中有效的。

那直接设置的方案使用没有找到可用的,最终的解决方案是,使用自己定义的rabbitListenerContainerFactory,

内容完全参照的spring-rabbit包中的SimpleRabbitListenerContainerFactory类,修改部分是:

@Override
    protected SimpleMessageListenerContainer createContainerInstance() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return container;
    }

这样手动确认就配置好了,手动确认消费者端的代码为:

@Service("msgSenderConsumerServiceImpl")
@RabbitListener(queues = "q.crm.msg")
public class MsgSenderConsumerServiceImpl {
    
    @RabbitHandler
    public void onMessage(@Payload String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
        //处理逻辑
        channel.basicAck(deliveryTag, false);
    }
    

以上~

 

posted @ 2017-08-09 22:56  ayouyj  阅读(924)  评论(0编辑  收藏  举报