阿里云rocketmq监听器自动订阅,客户端一对多

背景:

根据业务需求需要,需要实现一个rocketmq客户端扩展,可以连接多个rocketmq服务器以及注解化+配置化配置订阅关系

1.配置注解化:

封装了一个注解,包含了常用的mq监听器的配置

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface RocketMqMessageListener {

    /**
     * rocketmq实例
     */
    String rocketMqName();

    /**
     * 订阅的topic
     */
    String topic();

    /**
     * rocketmq的tag
     */
    String tag();

    /**
     * 是否开启消费者
     */
    String enable() default "true";

}

包扫描注解,借助spring读取加了RocketMqMessageListener注解的类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RocketMqListenerImporter.class)
public @interface RocketMqListenerScanner {

    String[] basePackages();

}

2.扫描监听器列表并且自动装配订阅关系:

2.1 注册包扫描的bean
public class RocketMqListenerImporter implements ImportBeanDefinitionRegistrar {
    ...
    
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取启动类上的RocketMqListenerScanner注解,并且获取basePackages配置
        Map<String, Object> maps = importingClassMetadata.getAnnotationAttributes(RocketMqListenerScanner.class.getName());
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(maps);
        String[] basePackages = annotationAttributes.getStringArray("basePackages");
        this.runnerRegistrarProcess(basePackages, registry);
}

    private void runnerRegistrarProcess(String[] basePackages, BeanDefinitionRegistry registry){
        //这边操作相当于@Bean
        //手动注册bean之前先把也basePackages的值放进去
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(RocketMqInitRunner.class);
        beanDefinition.setDependsOn("springUtil");
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("basePackages", basePackages);
        registry.registerBeanDefinition("rocketMqInitRunner", beanDefinition);
    }
    
    ...
}
2.2 扫描包含@RocketMqMessageListener的类

先扫描包下的所有类(不包含接口和抽象类):

...

public Set<Class<?>> doScanPackage(String[] basePackages){
    Set<Class<?>> classes = new HashSet<>();
    CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    for(String basePackage : basePackages){
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                .concat(ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage))
                        .concat("/**/*.class"));
        Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
        MetadataReader metadataReader;
        for (Resource resource : resources) {
            if (resource.isReadable()) {
                metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                try {
                    if (metadataReader.getClassMetadata().isConcrete()) {
                        //当类型不是抽象类或接口在添加到集合
                        classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
                    }
                } catch (Exception e) {
                    
                }
            }
        }
    }
    return classes;
}

...

在扫描后的结果集里进行筛选,这里的逻辑比较简单:

...
Set<Class<?>> listenerClasses = this.doScanPackage(basePackages);
//按名称归类各mq实例的订阅关系
Map<String, Map> subRelationMap = new LinkedHashMap<>();
if (CollectionUtil.isNotEmpty(listenerClasses)) {
    for (Class<?> listenerClass : listenerClasses) {
        RocketMqMessageListener listener = AnnotationUtils.findAnnotation(listenerClass, RocketMqMessageListener.class);
        if (listener == null) {
            continue;
        }
        
        ...
        //实现订阅关系自动装配功能,同时实现了一个监听器订阅多个topic
        if(topics.contains("|")){
            String[] topicStrs = topics.split("\|");
            Arrays.stream(topicStrs).distinct().forEach(topic -> createSubscriptionMap(subRelationMap, name, listenerClass, tag, topic));
        }else{
            createSubscriptionMap(subRelationMap, name, listenerClass, tag, topics);
        }
        
        ... 
       
    }
}
...
2.3 连接多mq服务端支持

原理是使用了springboot的map配置,且name属性和注解上的rocketMqName属性对应,然后启动相对应数量的rocketmq生产者和消费者客户端对象:

/**
 * 一组mq生产者&消费者配置
 * rocket.message.mq-config-map.rocket1.address
 *  ......
 * rocket.message.mq-config-map.rocket2.address
 */
private Map<String, RocketMqConfig> mqConfigMap;

3.解析配置:

3.1 简单的配置解析实现

这里的代码目的就是将配置填入注解中的占位符${}中,比如

@RocketMqMessageListener(rocketMqName = "rocket1", topic="${listener1.topic}", tag="*", enable = "${listener1.enable}")

示例代码中的listener1.enable和 listener1.topic 需要从配置中获取后填入:

...

private String valueAnalysis(String str){
    //类型1,类型2:需要解析
    if(str.startsWith("${") && str.endsWith("}")){
        str = str.substring(2, str.length()-1);
        Assert.notEmpty(str, "you cannot pass on ${} on a value");
        return handleExpressionValue(str);
    } else {
        //类型3 原样返回
        return str;
    }
}


private String handleExpressionValue(String str){
    //类型1 需要继续解析
    if(str.contains(":")){
        String[] strArray = str.split(":");
        str = propertyValueSet(strArray[0]);
        //spring 中找不到
        if(StringUtil.isNullOrEmpty(str)){
            //找冒号后面的值,找不到就抛错
            if(strArray.length == 1){
                Assert.notEmpty(str, "you cannot pass on ${"+ str +":} on a value");
            }
            //返回默认值
            return strArray[1];
        } else {
            //spring 中找到
            return propertyValueSet(str);
        }
    } else {
        //类型2 直接返回
        return propertyValueSet(str);
    }
}
3.2 获取属性值

获取值其实很简单,直接从spring环境中获取就好了:

@Override
protected String propertyValueSet(String str) {
    return environment.getProperty(str);
}

自此 一个简单的自动装配订阅关系的功能实现完成。

4.使用方式:

@RocketMqMessageListener(rocketMqName = "rocket1", topic="${listener1.topic}", tag="*", enable = "${listener1.enable}")
@SpringBootApplication
@RocketMqListenerScanner(basePackages = {"xxx.xxx.xxx.xxx"})
@EnableScheduling
public class AppStarter {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(AppStarter.class);
        springApplication.setAllowBeanDefinitionOverriding(true);
        springApplication.run(args);
    }

}

总结:

本文使用spring的元数据扫描自动装配了监听器订阅关系,并且简化了配置,将一些固定值的配置移到注解上,同时支持了一个监听器监听多个topic,使用了springboot的map配置支持了mq客户端连接多个mq服务端。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值