第一次接手老项目,我最懵的一件事是:同一个项目里,创建 bean 的姿势能有五六种——@component、@bean、factorybean、@import、xml、甚至运行时注册。到底选谁?标准究竟是什么?
下面把常见方式逐一拆开,顺手给你一张“怎么选”的脑图。
一、有哪些主流方式?
1) 组件扫描:@component 家族(含 @service/@repository/@controller)
怎么用
@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。
何时选:构造过程复杂、需要“拿结果不是拿工厂”的场景。
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
- 池/连接类务必正确清理。
配置类性能:@configuration(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(绕过代理)。 - 规则:所有需要切面的对象都由容器生产。
五、最佳实践清单(可直接落地)
- 优先用构造器注入,配合
final字段保证不可变与可测试性。 - 业务类用
@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方式的资料请关注代码网其它相关文章!
发表评论