Spring创建Bean的多种方式对比与最佳实践
目录
- 一、有哪些主流方式?
- 1) 组件扫描:@Component 家族(含 @Service/@Repository/@Controller)
- 2) Java 配置:@Configuration + @Bean
- 3) FactoryBean<T>:自定义工厂
- 4) @Import 家族:装配拼装器
- 5) XML(<bean/>)
- 6) 运行时注册:BeanDefinitionRegistry / GenericApplicationContext#registerBean
- 二、怎么选?——一张可落地的决策清单
- 三、组合拳:把方式与“条件/环境/范围/生命周期”拼起来
- 四、常见踩坑与规避
- 五、最佳实践清单(可直接落地)
- 写在最后
第一次接手老项目,我最懵的一件事是:同一个项目里,创建 Bean 的姿势能有五六种——@Component
、@Bean
、FactoryBean
、@Import
、XML、甚至运行时注册。到底选谁?标准究竟是什么?
下面把常见方式逐一拆开,顺手给你一张“怎么选”的脑图。
一、有哪些主流方式?
1) 组件扫描:@Component 家族(含 @Service/@Repository/@C编程客栈ontroller)
怎么用
@Component public class OrderService { }
特点
- 简单、直观、与分层语义强绑定(
@Service
等)。 - 适合常规业务类;依赖通过构造器注入最佳。
- 与 AOP、校验、事务天然契合。
何时选:90% 的业务类、无外部构造复杂度的 Bean。
2) Java 配置:@Configuration + @Bean
怎么用
@Configuration public class AppConfig { @Bean public IdGenerator idGenerator() { return new SnowflakeIdGenerator(...); } }
特点
- 显式声明生命周期、构造参数、工厂方法。
- 可精细控制作用域、
init/destroy
、@Conditional
、@Profile
等。 - 注意:
@Configuration
默认proxyBeanMethods = true
(Full 模式,会 CGLIB 代理确保单例跨 @Bean 调用);在不需要跨方法引用保障的场景可设false
(Lite 模式)提升启动性能。
何时选:
- 需要精细构造/第三方库对象(DataSource、ObjectMapper、ThreadPool、KafkaClient…)。
- 需要搭配条件装配/环境隔离时。
3) FactoryBean<T>:自定义工厂
怎么用
@Component public class ClientFactoryBean implements FactoryBean<Client> { public Client getObject() { return new Client(config()); } public Class<?> getObjectType() { return Client.class; } }
特点
- Bean 的“生产逻辑”可编程化,适合复杂/延迟/池化创建。
- 获取“工厂本身”用:
&clientFactoryBean
。 - 便于封装复杂 SDK 实例化、动态代理实例、框架级 Bean。
何时选:构造过程复杂、js需要“拿结果不是拿工厂”的场景。
4) @Import 家族:装配拼装器
怎么用
- 直接导入配置类:
@Import(AppConfig.class)
- 选择器:
ImportSelector
(按条件返回类名集合) - 低层注册:
ImportBeanDefinitionRegistrar
(手动注册BeanDefinition
)
特点
- 适合模块化配置/Starter:把一组 Bean 一键装配。
- 与
@Conditional
组合,实现“自动装配”的开/关。
何时选:框架/组件开发、Starter、按类路径/环境动态装配。
5) XML(<bean/>)
特点
- 历史包袱/遗留系统常见;与 JavaConfig 可并存。
- 在强合规/模板化平台仍有价值。
何时选:遗留项目、平台强约束、需运行时热替换 XML 的场景。
6) 运行时注册:BeanDefinitionRegistry / GenericApplicationContext#registerBean
怎么用
context.registerBean("userRepo", UserRepo.class, () -> new UserRepo(ds));
特点
- 最灵活:可在运行中按条件装配/卸载。
- 常用于框架扩展、动态多租户/插件化。
何时选:框架层、插件/脚手架、动态场景。
二、怎么选?——一张可落地的决策清单
- 普通业务类 →
@Component
(或语义化的@Service/@Repository
),配合构造器注入。 - 第三方对象/需要精细控制(连接池、客户端、线程池)→
@Configuration + @Bean
。 - 创建逻辑复杂/需要返回“产品而非工厂” →
FactoryBean
。 - 模块化/Starter/按条件成批装配 →
@Import
(ImportSelector/Registrar
)+@Conditional
。 - 遗留或平台要求 → XML。
- 动态注册/插件化 → 运行时注册 API。
高频口诀:“业组件、库配 Bean、难用工厂、批量用 Import、老活交 XML、动态上 Registry。”
三、组合拳:把方式与“条件/环境/范围/生命周期”拼起来
条件装配:@Conditional
/ @Profile
- 例:仅在
prod
激活某个@Bean
;当类路径存在某依赖再装配。
作用域:@Scope("singleton"|"prototype"|"request"|"session")
- 单例配
@Bean
/@Component
;原型慎用(生命周期管理在你)。
懒加载:@Lazy
- 对重量级 Bean 延迟创建,缩短冷启动。
优先/歧义:@Primary
/ @Qualifier("xxx")
- 多实现注入时避免“多候选”异常。
生命周期:initMethod
/destroyMethod
、@PostConstruct
/@PreDestroy
- 池/连接类务必正确清理。
配置类性能:@Configuratjavascription(proxyBeanMethods = false)
- 若
@Bean
之间不相互调用,设 false 提升启动性能。
四、常见踩坑与规避
- 同名/同类型冲突
- 现象:
NoUniqueBeanDefinitionException
- 解法:约定 Bean 命名;
@Qualifier
精确注入;需要默认实现时加@Primary
。
- 原型 Bean 生命周期失控
- 容器只负责创建,不托管销毁;注入到单例里极易内存/状态泄漏。
- 方案:
ObjectProvider
/Provider
延迟获取,或改成无状态。
@Configuration
误用导致“重复实例化”
- 在 Full 模式下通过代理保证同一
@Bean
单例复用; - 设
proxyBeanMethods=false
时跨方法互调会各自新建,需谨慎。
FactoryBean
取到的是“产品不是工厂”
- 想拿工厂本身要用
&beanName
。这点面试常考。
- 条件/环境不生效
- 激活 profile 写错;
@ConditionalOnClass
等判断失败。 - 启动参数/环境变量要对齐:
spring.profiles.active=prod
。
- AOP/事务不起效
- Bean 未交给容器管理、或在
@Bean
方法里手动new
(绕过代理)。 - 规则:所有需要切面的对象都由容器生产。
五、最佳实践清单(可直接落地)
- 优先用构造器注入,配合
finapythonl
字段保证不可变与可测试性。 - 业务类用
@Component
家族,第三方对象用@Bean
,职责清晰。 - Starter/框架层用
@Import + Conditional
,形成模块化装配。 - 对重量 Bean 加
@Lazy
,对多实现用@Qualifier
,给默认实现标@Primary
。 - 配置类若无跨 @Bean 互调,开启
@Configuration(proxyBeanMethods = false)
提升启动性能。 - 需要复杂构造/代理产物,优先考虑
FactoryBean
封装。 - 原型 Bean 慎用,确需动态实例用
ObjectProvider
拉取。 - 生命周期要闭环:连接/线程池显式
destroyMethod
,或实现DisposableBean
。
写在最后
把“谁创建、何时创建、在哪创建、如何销毁”说清楚,就是 Bean 策略的全部。 业务常态化用 @Component
;第三方与精细控制用 @Bean
;复杂构造上 FactoryBean
;模块化上 @Import
;其余按场景增量选择。 选对方式,系统启动快、结构清晰、扩展成本更低。
以上就是Spring创建Bean的多种方式对比与最佳实践的详细内容,更多关于Spring创建Bean方式的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论