问题
@RocketMQMessageListener的属性(例如:topic和consumerGroup等)仅支持环境变量替换,无法支持SPEL。无法像SpringMVC的@RequestParam一样。
ListenerContainerConfiguration下通过实现SmartInitializingSingleton后,在afterSingletonsInstantiated中进行处理和注册DefaultRocketMQListenerContainer。
如下图,处理过程中从注解获取的属性仅进行了环境变量替换。未支持SPEL。
解决方法
1、从源码得知,方法的执行是通过SmartInitializingSingleton进行触发,因此我们可以提前获取,注解并扩展所需的功能。
由于SmartInitializingSingleton是在bean全部执行完后,再进行的处理。因此可以自定义bean触发扩展逻辑处理
2、要支持变量替换,可以参考@RequestParam的逻辑实现
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument方法内resolveEmbeddedValuesAndExpressions的处理逻辑
具体实现代码:
@Component
public class RocketMQListenerExtendHandler implements InitializingBean, BeanFactoryAware {
@Autowired
private ApplicationContext applicationContext;
private ConfigurableBeanFactory configurableBeanFactory;
private BeanExpressionContext expressionContext;
@Override
public void afterPropertiesSet() throws Exception {
//获取RocketMQMessageListener相关bean
Map<String,Object> beans = applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class);
for (Object bean : beans.values()){
if (!RocketMQListener.class.isAssignableFrom(bean.getClass())) {
continue;
}
//通过反射获取注解内的属性
Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
Field field = invocationHandler.getClass().getDeclaredField("memberValues");
field.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
for (Map.Entry<String,Object> entry: memberValues.entrySet()) {
if(Objects.nonNull(entry) && (entry.getValue() instanceof String)){
//根据注解属性进行自定义扩展
memberValues.put(entry.getKey(), handlePlaceholderAndSPEL((String) entry.getValue()));
}
}
}
}
//根据注解属性进行自定义扩展
private String handlePlaceholderAndSPEL(String param) {
return (String)resolveEmbeddedValuesAndExpressions(param);
}
//仿照@RequestParam的实现逻辑进行 变量替换 和 SPEL表达式执行
private Object resolveEmbeddedValuesAndExpressions(String value) {
if (this.configurableBeanFactory != null && this.expressionContext != null) {
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
return exprResolver == null ? placeholdersResolved : exprResolver.evaluate(placeholdersResolved, this.expressionContext);
} else {
return value;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
this.expressionContext = configurableBeanFactory != null ? new BeanExpressionContext(configurableBeanFactory, new RequestScope()) : null;
}
}
}