当前位置: 首页 > news >正文

RabbitMQ基础教程之基于配置的消费者实现

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

RabbitMQ基础教程之基于配置的消费者实现

相关博文,推荐查看:

  1. RabbitMq基础教程之安装与测试
  2. RabbitMq基础教程之基本概念
  3. RabbitMQ基础教程之基本使用篇
  4. RabbitMQ基础教程之使用进阶篇
  5. RabbitMQ基础教程之Spring&JavaConfig使用篇
  6. RabbitMQ基础教程之Spring-JavaConfig-FactoryBean使用姿势

前面一篇介绍了使用工厂方式创建消费者,其中一个不太友好的地方就在配置都是硬编码的方式,不太灵活,那么是否可以结合前一篇的FactoryBean来实现从配置中来灵活的创建消费者呢?

I. 动态配置实现消费者程序

1. 配置文件加载

首先就是需要从配置文件中获取相应的配置信息,借助JavaConfig,加一个注解即可

@Configuration
@PropertySource("classpath:dynamicConfig.properties")
public class DynSpringConfig {

    @Autowired
    private Environment environment;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost(environment.getProperty("dyn.mq.host"));
        factory.setPort(Integer.parseInt(environment.getProperty("dyn.mq.port")));
        factory.setUsername(environment.getProperty("dyn.mq.uname"));
        factory.setPassword(environment.getProperty("dyn.mq.pwd"));
        factory.setVirtualHost(environment.getProperty("dyn.mq.vhost"));
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }
}

主要就是 @PropertySource("classpath:dynamicConfig.properties") , 表示从dynamicConfig.properties文件中读取相应的配置,而这些配置,会存放在 Environment 容器内;

获取配置的方式,就是通过org.springframework.core.env.PropertyResolver#getProperty(java.lang.String)获取

2. 消费者通用实现

实现一个简单的通用的消费端,主要根据前一篇博文中定义的MQContainerFactory,来生成SimpleMessageListenerContainer,然后注入消费服务,并启动容器

public class DynamicConsumer {
    public DynamicConsumer(MQContainerFactory fac) throws Exception {
        SimpleMessageListenerContainer container = fac.getObject();
        container.setMessageListener(new AbsMQConsumer() {
            @Override
            public boolean process(Message message, Channel channel) {
                System.out.println("DynamicConsumer: " + fac.getQueue() + " | " + new String(message.getBody()));
                return true;
            }
        });

        container.start();
    }
}

上面是一个非常简单的实现,针对常见的的RabbitMQ消息消费而言,也可以写一个泛型类,然后借助Spring的事件机制,实现一个通用的消费端,一种case如下:

public class JsonMsgConsumer {
    public JsonMsgConsumer(ApplicationContext apc, MQContainerFactory fac, Class<?> msgType) throws Exception {
        SimpleMessageListenerContainer container = fac.getObject();
        container.setMessageListener(new AbsMQConsumer() {
            @Override
            public boolean process(Message message, Channel channel) {
                System.out.println("DynamicConsumer: " + fac.getQueue() + " | " + new String(message.getBody()));

                Object type = JSONObject.parseObject(message.getBody(), msgType);
                apc.publishEvent(type);
                return true;
            }
        });

        container.start();
    }
}

如果message中的数据,是通过Json序列化方式存入,则使用方,只需要监听对应的Event消费数据即可,完全不用再关系消费端的情况了

3. MQContainerFactory 初始化

根据配置文件中的信息,初始化factory,这个可谓是最关键的地方了,实现也和之前大致类似,只不过是将硬编码改成配置信息读取而已,完整的配置文件如下

@Configuration
@PropertySource("classpath:dynamicConfig.properties")
public class DynSpringConfig {

    @Autowired
    private Environment environment;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost(environment.getProperty("dyn.mq.host"));
        factory.setPort(Integer.parseInt(environment.getProperty("dyn.mq.port")));
        factory.setUsername(environment.getProperty("dyn.mq.uname"));
        factory.setPassword(environment.getProperty("dyn.mq.pwd"));
        factory.setVirtualHost(environment.getProperty("dyn.mq.vhost"));
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public AmqpProducer amqpProducer() {
        return new AmqpProducer();
    }


    @Bean
    public DynamicConsumer dynamicConsumer(ConnectionFactory connectionFactory, RabbitAdmin rabbitAdmin)
            throws Exception {
        MQContainerFactory fac = MQContainerFactory.builder().directExchange(environment.getProperty("dyn.mq.exchange"))
                .queue(environment.getProperty("dyn.mq.queue"))
                .autoDeleted(Boolean.parseBoolean(environment.getProperty("dyn.mq.autoDeleted")))
                .autoAck(Boolean.parseBoolean(environment.getProperty("dyn.mq.autoAck")))
                .durable(Boolean.parseBoolean(environment.getProperty("dyn.mq.durable")))
                .routingKey(environment.getProperty("dyn.mq.routingKey")).rabbitAdmin(rabbitAdmin)
                .connectionFactory(connectionFactory).build();

        return new DynamicConsumer(fac);
    }
}

4. 测试

配置文件内容:

dyn.mq.host=127.0.0.1
dyn.mq.port=5672
dyn.mq.uname=admin
dyn.mq.pwd=admin
dyn.mq.vhost=/
dyn.mq.exchange=fac.direct.exchange
dyn.mq.queue=dyn.queue
dyn.mq.durable=true
dyn.mq.autoDeleted=false
dyn.mq.autoAck=false
dyn.mq.routingKey=fac-routing

测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DynSpringConfig.class)
public class DynamicConsumerUnit {
    @Autowired
    private AmqpProducer amqpProducer;

    @Test
    public void testDirectConsumer() throws InterruptedException {
        String[] routingKey = new String[]{"hello.world", "fac-routing", "test1"};
        for (int i = 0; i < 100; i++) {
            amqpProducer.publishMsg("fac.direct.exchange", routingKey[i % 3],
                    ">>> hello " + routingKey[i % 3] + ">>> " + i);
        }
        System.out.println("-------over---------");

        Thread.sleep(1000 * 60 * 10);
    }
}

执行之后,就可以看到正常的消费了

5. 扩充与小结

看完之后,可能有一个问题,为什么要这样做,好处是什么?

大部分的时候,从MQ获取消息的逻辑都一样,唯一的区别在于获取到数据之后做的业务而言,如果把这一块完全的抽象出来,通过配置的方式,那么额外的新增mq的消费,就不需要再改消费端的代码了,然后就会有一个疑问,上面的配置文件中,生成dynamicConsumer的bean不也是需要额外写么?

如果将配置信息,以某种数组的方式定义,遍历读取这些配置,然后创建多个DynamicConsuer实例,是否就能支持动态扩展呢?

将配置改成下面的进行尝试

@Configuration
@PropertySource("classpath:dynamicConfig.properties")
public class DynSpringConfig {

    @Autowired
    private Environment environment;

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost(environment.getProperty("dyn.mq.host"));
        factory.setPort(Integer.parseInt(environment.getProperty("dyn.mq.port")));
        factory.setUsername(environment.getProperty("dyn.mq.uname"));
        factory.setPassword(environment.getProperty("dyn.mq.pwd"));
        factory.setVirtualHost(environment.getProperty("dyn.mq.vhost"));
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public AmqpProducer amqpProducer() {
        return new AmqpProducer();
    }


    @Autowired
    private ConnectionFactory connectionFactory;

    @PostConstruct
    public void dynamicConsumer()
            throws Exception {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        MQContainerFactory fac = MQContainerFactory.builder().directExchange(environment.getProperty("dyn.mq.exchange"))
                .queue(environment.getProperty("dyn.mq.queue"))
                .autoDeleted(Boolean.parseBoolean(environment.getProperty("dyn.mq.autoDeleted")))
                .autoAck(Boolean.parseBoolean(environment.getProperty("dyn.mq.autoAck")))
                .durable(Boolean.parseBoolean(environment.getProperty("dyn.mq.durable")))
                .routingKey(environment.getProperty("dyn.mq.routingKey")).rabbitAdmin(rabbitAdmin)
                .connectionFactory(connectionFactory).build();

        new DynamicConsumer(fac);
    }
}

注意之前 dynamicConsumer 是bean的创建,改成了初始化一个实例,如果配置文件是数组,内部用一个遍历就可以全部加载,现在就需要验证上面的配置改动之后,是否依然可以消费数据

实测ok,部分输出如下

-------over---------
DynamicConsumer: dyn.queue | >>> hello fac-routing>>> 1
DynamicConsumer: dyn.queue | >>> hello fac-routing>>> 4
DynamicConsumer: dyn.queue | >>> hello fac-routing>>> 7

II. 其他

项目地址

  • 六月/study-demo

一灰灰Blog: https://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840

扫描关注

QrCode

转载于:https://my.oschina.net/u/566591/blog/1827349

相关文章:

  • 上传文件过大的问题FileUploadBase$SizeLimitExceededException
  • 数据库index
  • What Is Cocoa?
  • JAVA 学习IO流
  • lightoj 1013 dp
  • php中curl和soap方式请求服务超时问题
  • 8月25日
  • 冒泡排序和选择排序流程图
  • 域帐号密码过期邮件提醒
  • 一个html,3D 标签 鼓励自己
  • 阿里云大数据MaxCompute基于UDTF解析JSON日志的案例
  • The connection to adb is down, and a severe error has occured. 错误
  • 一文带你了解 LSM Compaction
  • 里氏替换原则
  • UI设计不就是画线框,凭什么年薪30W?
  • 【5+】跨webview多页面 触发事件(二)
  • 【笔记】你不知道的JS读书笔记——Promise
  • css选择器
  • eclipse的离线汉化
  • javascript数组去重/查找/插入/删除
  • JS实现简单的MVC模式开发小游戏
  • zookeeper系列(七)实战分布式命名服务
  • 猴子数据域名防封接口降低小说被封的风险
  • 解决iview多表头动态更改列元素发生的错误
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 前端
  • 十年未变!安全,谁之责?(下)
  • 什么软件可以剪辑音乐?
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 说说我为什么看好Spring Cloud Alibaba
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • (03)光刻——半导体电路的绘制
  • (2)(2.10) LTM telemetry
  • (23)Linux的软硬连接
  • (C++17) std算法之执行策略 execution
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • .jks文件(JAVA KeyStore)
  • .Net Core 中间件验签
  • .net framework4与其client profile版本的区别
  • .net6 webapi log4net完整配置使用流程
  • [ solr入门 ] - 利用solrJ进行检索
  • [ 渗透工具篇 ] 一篇文章让你掌握神奇的shuize -- 信息收集自动化工具
  • [Android View] 可绘制形状 (Shape Xml)
  • [Android]Tool-Systrace
  • [autojs]autojs开关按钮的简单使用
  • [BZOJ] 2044: 三维导弹拦截
  • [CSS3备忘] transform animation 等
  • [Docker]六.Docker自动部署nodejs以及golang项目
  • [Flutter]打包IPA
  • [JavaWeb]—Spring入门
  • [leetcode 数位计算]2520. 统计能整除数字的位数