Spring中GenericApplicationContext编程式注册Bean注入List异常问题
Spring编程式注册Bean注入List的不一致问题解析
问题背景
我们定义了MyAdapterService接口,以及依赖注入该接口实例列表的MyGenericService。测试发现不同Bean注册方式下,实例注入List的表现不一致:
- 使用
@Service类级注解、@Configuration中的@Bean方法注册的Bean可正常注入; - 通过
GenericApplicationContext在@Configuration构造方法中编程式注册的Bean,无法稳定注入到List中:- 配置类无
@Bean方法时,编程式注册的Bean完全无法注入; - 配置类有
@Bean方法时,注入情况随消费者不同而不稳定。
- 配置类无
已确认编程式注册的Bean可立即通过getBean获取,核心疑问如下:
- Spring为何不在所有
@Configuration实例化后再解析Bean列表?能否提示Spring等待GenericApplicationContext配置完成? - 新增
@Bean方法为何会影响注入结果?是否与静态分析有关?
核心疑问解答
1. Spring的Bean解析时机与编程式注册的冲突
Spring上下文初始化流程中,@Configuration类的处理分为两个关键阶段:
- 配置类解析阶段:Spring会先对所有
@Configuration类做静态分析(比如扫描@Bean方法),提前收集Bean定义,为后续依赖注入的候选Bean列表做准备; - 配置类实例化阶段:当需要使用配置类中的Bean时,才会实例化配置类,执行构造方法和
@Bean方法。
你在@Configuration构造方法中做编程式注册,这个时机处于配置类实例化阶段,而依赖注入的候选Bean列表在更早的解析阶段就已初步确定。如果配置类没有@Bean方法,Spring会将其判定为普通Bean,不会触发解析后的后续补充处理,导致构造方法里注册的Bean无法进入依赖注入候选池;若配置类有@Bean方法,Spring会在处理@Bean时触发部分上下文刷新,此时编程式注册的Bean可能被纳入候选池,但不同消费者的初始化时机存在差异,就会出现注入不稳定的情况。
要确保编程式注册的Bean被所有依赖注入识别,可更换注册时机:
- 实现
ApplicationContextInitializer接口,在上下文刷新前完成编程式Bean注册; - 使用配置类中带
@PostConstruct注解的方法替代构造方法注册Bean,该注解方法在配置类实例化后、依赖注入完成前执行,此时上下文已完成基础解析,注册的Bean会被纳入候选池; - 实现
BeanDefinitionRegistryPostProcessor接口,这是Spring专门用于动态注册Bean定义的扩展点,执行时机在所有配置类解析之后、Bean实例化之前,能保证注册的Bean被所有依赖注入正确识别。
2. 新增@Bean方法对注入结果的影响
确实和Spring对@Configuration类的静态分析直接相关:
- 当配置类没有
@Bean方法时,Spring会将其当作普通@Component类处理,仅在需要实例化该类时才执行构造方法,而此时依赖注入的候选Bean列表已经确定,因此构造方法里注册的Bean无法被MyGenericService的List注入识别; - 当配置类有
@Bean方法时,Spring会将其标记为full模式配置类(默认行为),提前对配置类做CGLIB代理,并且在上下文初始化时优先处理@Bean方法的解析和注册,这个过程会触发上下文的部分刷新,此时构造方法中注册的Bean会被纳入候选池。但不同消费者(比如MyGenericService和其他依赖MyAdapterService的Bean)的初始化时机可能存在差异,如果某个消费者在编程式注册完成前就完成了依赖注入,就会出现注入不全的情况,这就是你看到的“不稳定”现象。
另外,full模式的配置类会被代理,@Bean方法的执行节奏也会间接影响上下文刷新,进一步导致编程式注册Bean的识别时机存在波动。
内容的提问来源于stack exchange,提问作者Puce
相关产品推荐
相关产品推荐

