RocketMq tag动态设置失效问题

问题:Springboot整合RocketMQ后动态tag设置失效!(最新版本已经解决此问题)

依赖:

<!--    rocketMQ    -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>

写法:

@Slf4j
@Component
@RocketMQMessageListener(nameServer = "${rocketmq.payment.nameServer}", topic = "${rocketmq.payment.refund.topic}",consumerGroup = "${rocketmq.payment.refund.groupName}"
        ,consumeMode = ConsumeMode.ORDERLY, selectorExpression = "${rocketmq.payment.refund.tag}")
public class RunfundUpdateListener implements RocketMQListener<String> {

    
    @Override
    public void onMessage(String message) {

        log.info("into#class:{}, mesasage:{}", "RunfundUpdateListener", message);
       
    }

排查:RocketMQMessageListener生效的类为:ListenerContainerConfiguration 

@Configuration
public class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton {
    private final static Logger log = LoggerFactory.getLogger(ListenerContainerConfiguration.class);

    private ConfigurableApplicationContext applicationContext;

    private AtomicLong counter = new AtomicLong(0);

    private StandardEnvironment environment;

    private RocketMQProperties rocketMQProperties;

    private ObjectMapper objectMapper;

    public ListenerContainerConfiguration(ObjectMapper rocketMQMessageObjectMapper,
        StandardEnvironment environment,
        RocketMQProperties rocketMQProperties) {
        this.objectMapper = rocketMQMessageObjectMapper;
        this.environment = environment;
        this.rocketMQProperties = rocketMQProperties;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void afterSingletonsInstantiated() {
        Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class);

        if (Objects.nonNull(beans)) {
            beans.forEach(this::registerContainer);
        }
    }

    private void registerContainer(String beanName, Object bean) {
        Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);

        if (!RocketMQListener.class.isAssignableFrom(bean.getClass())) {
            throw new IllegalStateException(clazz + " is not instance of " + RocketMQListener.class.getName());
        }

        RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);
        validate(annotation);

        String containerBeanName = String.format("%s_%s", DefaultRocketMQListenerContainer.class.getName(),
            counter.incrementAndGet());
        GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext;

        genericApplicationContext.registerBean(containerBeanName, DefaultRocketMQListenerContainer.class,
            () -> createRocketMQListenerContainer(containerBeanName, bean, annotation));
        DefaultRocketMQListenerContainer container = genericApplicationContext.getBean(containerBeanName,
            DefaultRocketMQListenerContainer.class);
        if (!container.isRunning()) {
            try {
                container.start();
            } catch (Exception e) {
                log.error("Started container failed. {}", container, e);
                throw new RuntimeException(e);
            }
        }

        log.info("Register the listener to container, listenerBeanName:{}, containerBeanName:{}", beanName, containerBeanName);
    }

    private DefaultRocketMQListenerContainer createRocketMQListenerContainer(String name, Object bean, RocketMQMessageListener annotation) {
        DefaultRocketMQListenerContainer container = new DefaultRocketMQListenerContainer();

        String nameServer = environment.resolvePlaceholders(annotation.nameServer());
        nameServer = StringUtils.isEmpty(nameServer) ? rocketMQProperties.getNameServer() : nameServer;
        String accessChannel = environment.resolvePlaceholders(annotation.accessChannel());
        container.setNameServer(nameServer);
        if (!StringUtils.isEmpty(accessChannel)) {
            container.setAccessChannel(AccessChannel.valueOf(accessChannel));
        }
        container.setTopic(environment.resolvePlaceholders(annotation.topic()));
        container.setConsumerGroup(environment.resolvePlaceholders(annotation.consumerGroup()));
        container.setRocketMQMessageListener(annotation);
        container.setRocketMQListener((RocketMQListener) bean);
        container.setObjectMapper(objectMapper);
        container.setName(name);  // REVIEW ME, use the same clientId or multiple?

        return container;
    }

    private void validate(RocketMQMessageListener annotation) {
        if (annotation.consumeMode() == ConsumeMode.ORDERLY &&
            annotation.messageModel() == MessageModel.BROADCASTING) {
            throw new BeanDefinitionValidationException(
                "Bad annotation definition in @RocketMQMessageListener, messageModel BROADCASTING does not support ORDERLY message!");
        }
    }
}

逻辑:

Spring所有的单例类实例化后本类的afterSingletonsInstantiated()方法中会找到所有被RocketMQMessageListener注解标记的实例,将value通过createRocketMQListenerContainer()方法变为DefaultRocketMQListenerContainer对象,然后继续被Spring管理.

在createRocketMQListenerContainer()方法中container.setRocketMQMessageListener(annotation);方法设置tag,代码如下:

public void setRocketMQMessageListener(RocketMQMessageListener anno) {
        this.rocketMQMessageListener = anno;

        this.consumeMode = anno.consumeMode();
        this.consumeThreadMax = anno.consumeThreadMax();
        this.messageModel = anno.messageModel();
        this.selectorExpression = anno.selectorExpression();
        this.selectorType = anno.selectorType();
        this.consumeTimeout = anno.consumeTimeout();
    }

也就是说我们设置一个表达式比如用例中的:selectorExpression = "${rocketmq.payment.refund.tag}" 代码会去找 tag="${rocketmq.payment.refund.tag}"的数据 而不是根据配置解析具体配置的tag再去找。所以动态配置不生效。

 

解决方案:

一、修改ListenerContainerConfiguration的代码代码:变为

beanBuilder.addPropertyValue("selectorExpress", this.environment.resolvePlaceholders(annotation.selectorExpression()));

二、Spring容器启动后再改该实例的值。提供一个示例代码,使所有consumer可以动态设置tag(未测试)

@Component
public class ChangeSelectorExpressionBeforeMqStart implements InitializingBean, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Autowired
    private StandardEnvironment standardEnvironment;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {

        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class);
        if(MapUtils.isNotEmpty(beansWithAnnotation)){
            beansWithAnnotation.entrySet().stream().forEach(entry ->{
                Object value = entry.getValue();
                RocketMQMessageListener annotation = value.getClass().getAnnotation(RocketMQMessageListener.class);
                String selectorExpression = annotation.selectorExpression();
                String realSelectorExpression = standardEnvironment.resolvePlaceholders(selectorExpression);
                // 获取代理处理器
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                // 获取私有 memberValues 属性
                Field f = null;
                try {
                    f = invocationHandler.getClass().getDeclaredField("memberValues");
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
                f.setAccessible(true);
                // 获取实例的属性map
                Map<String, Object> memberValues = null;
                try {
                    memberValues = (Map<String, Object>) f.get(invocationHandler);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                // 修改属性值
                memberValues.put("selectorExpression", realSelectorExpression);
            });
        }
    }

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值