掉进了Spring @Configuration 坑里,导致阿里云 RocketMq 消费者状态(同一个进程出了两client)异常
1 案发现场
复现代码
@Configuration
public class BeanConflict {
@Bean("MyAtomic1")
public MyAtomic getMyAtomic(@Value("${OONN}") String oonn){
System.out.println(oonn);
System.out.println("--------first--------");
return new MyAtomic();
}
@Bean("MyAtomic")
public MyAtomic getMyAtomic(){
System.out.println("---------------------");
System.out.println("--------second--------");
return new MyAtomic();
}
}
2 源码分析 基于 spring-context 5.1.6
refresh
----->org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors // 解析Spring Bean(定义)
// 遍历工厂后处理器 org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口实现类 目的是修改 内部 bean factory
// 此时 configuration class
----->org.springframework.context.annotation.ConfigurationClassPostProcessor // Spring Bean ConfigurationClass 后处理器
// 里面执行了所有的Parse each @Configuration class 注解的类 最简单的项目也有80-90个 @Configuration class 放入一个名为configClasses 的 LinkedHashSet中
// 逐一解析,生成Spring Bean====>//class级别
---->org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
// 加载Spring Bean 依据 BeanMethod
// 内部逐一解析 ,生成Spring Bean====>//method级别
----->org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
// 如果beanName相同会进行重载判断(本文不关心)
---->org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#isOverriddenByExistingDefinition
// 通常会产生 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition 类型的 Spring Bean 并放到 org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap 中,此时会有
---->// Cannot modify startup-time collection elements anymore (for stable iteration)
---->org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
---->synchronized (this.beanDefinitionMap) { ...}同步执行
// 如果一个类多个 BeanMethod重复
// 循环--->内部逐一解析 ,生成Spring Bean====>//method级别
----->org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
---->org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
// 循环---> 逐一解析,生成Spring Bean====>//class级别
如下图
不同方法
相同方法名
// 经过refresh的其他处理终于来到了 bean实例化,属性设置,初始化
----->org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons // 实例化所有Spring Bean
---->org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance // 1159行 会执行一个工厂方法的获取
// 同理MyAtomic2 Spring Bean的创建
// 确定了使用工厂方法来实例化bean
---->org.springframework.beans.factory.support.ConstructorResolver#instantiateUsingFactoryMethod // 匹配具体的方法
// 怎么匹配的呢?Choose this factory method if it represents the closest match.
// 算法解释 不是宽大模式也不是严格模式下,找距离最近(最小)的方法
// 而上面说的方法列表是org.springframework.beans.factory.support.ConstructorResolver#getCandidateMethods决定的 Arraylist
// beanName MyAtomic2 MyAtomic1 执行的反射方法一模一样!!!
// 结果就是调用相同逻辑来实例化bean----->同一个妈生的!!!
// 线上情况就是
-----> Object result = factoryMethod.invoke(factoryBean, args); // 反射获取 // 注意第一个参数 factoryBean 是CGLIB代理类
----->callback 做织入(参数依赖等获取如第一个参数@Value获取)--->org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept
----->重复执行客户端构建逻辑,于是两个相同订阅的com.aliyun.openservices.ons.api.order.OrderConsumer 就出来了!两个客户端走了相同的订阅!topic
3 小结
对于 @Configuration + @Bean 注解一定要区分方法名,因为Spring Bean实例化使用的工厂方法,反射(+AOP)来生是生成bean实例,如果方法区分度很低那就有问题了!!
所以像配置rocketMq 客户端之类的Bean,一定要区分方法名,copy code 必须重新命名方法名!