问题: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);
});
}
}
}