Spring Bean扩展的正确方式探讨:自定义Starter场景疑问
你的这种扩展思路是Spring Boot Starter的标准实践之一:Starter提供默认Bean实现,用户通过自定义子类替换默认实现,自动装配时优先使用用户自定义的Bean。但当前出现默认BaseBean被实例化后未销毁的问题,核心原因是条件判断时机不正确,导致Starter的默认Bean在用户自定义Bean注册前就完成了初始化。
为什么会出现双实例?
@ConditionalOnMissingBean(BaseBean.class)的判断逻辑是:在当前配置类处理阶段,若容器中不存在BaseBean类型(包括子类)的Bean定义,则创建默认Bean。如果你的Starter自动配置类加载时机早于用户AppBean的组件扫描,那么在执行getBean()方法时,AppBean还未被注册到容器,条件判断为真,默认BaseBean被实例化;后续AppBean被扫描注册后,由于它是BaseBean的子类,会成为自动装配的优先候选,但已创建的默认BaseBean实例不会被Spring主动销毁,从而出现双实例的情况。
正确的扩展方式调整
要让这种扩展逻辑正常工作,需确保默认Bean仅在用户未提供自定义实现时才被创建,具体步骤如下:
1. 正确配置Starter的自动加载
在Starter的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,添加自动配置类的全类名:
com.example.starter.StarterConfiguration
Spring Boot会保证自动配置类的加载时机晚于用户自定义的@Component扫描,此时AppBean已被注册,@ConditionalOnMissingBean会正确判断,不会创建默认BaseBean。
2. 确保用户自定义Bean被扫描到
确认AppBean所在的包被主启动类的@ComponentScan覆盖(主启动类默认扫描自身包及其子包),若不在默认扫描路径,需手动指定:
@SpringBootApplication(scanBasePackages = {"com.example.app", "com.example.starter"}) public class AppApplication { public static void main(String[] args) { SpringApplication.run(AppApplication.class, args); } }
3. 优化条件判断的匹配范围
可以显式指定@ConditionalOnMissingBean的搜索策略,确保检查整个容器的Bean定义:
@Configuration class StarterConfiguration { @Bean @ConditionalOnMissingBean(value = BaseBean.class, search = SearchStrategy.ALL) BaseBean getBean() { return new BaseBean(); } }
SearchStrategy.ALL是默认值,此配置可增强可读性,明确表示检查所有已注册的Bean。
额外说明
- 自动装配时优先选择
AppBean是正常行为:当容器中存在BaseBean类型的子类Bean时,Spring会优先选择更具体的类型作为自动装配候选;若需更精确控制,可给AppBean添加@Primary注解,明确标记为优先Bean。 - 若已出现默认Bean未销毁的情况,只能通过调整加载时机避免其被创建,Spring不会主动销毁已初始化的Bean。
内容的提问来源于stack exchange,提问作者hugh

