Spring 创建 Bean 的多种方式对比与最佳实践

原文来自于:zha-ge.cn/java/124

Spring 创建 Bean 的多种方式对比与最佳实践

第一次接手老项目,我最懵的一件事是:同一个项目里,创建 Bean 的姿势能有五六种 ------@Component、@Bean、FactoryBean、@Import、XML、甚至运行时注册。到底选谁?标准究竟是什么?

下面把常见方式逐一拆开,顺手给你一张"怎么选"的脑图。


一、有哪些主流方式?

1) 组件扫描:@Component 家族(含 @Service/@Repository/@Controller)

怎么用

java 复制代码
@Component
public class OrderService { }

特点

  • 简单、直观、与分层语义强绑定(@Service 等)。
  • 适合常规业务类;依赖通过构造器注入最佳。
  • 与 AOP、校验、事务天然契合。

何时选:90% 的业务类、无外部构造复杂度的 Bean。


2) Java 配置:@Configuration + @Bean

怎么用

java 复制代码
@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>:自定义工厂

怎么用

java 复制代码
@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

怎么用

java 复制代码
context.registerBean("userRepo", UserRepo.class, () -> new UserRepo(ds));

特点

  • 最灵活:可在运行中按条件装配/卸载。
  • 常用于框架扩展、动态多租户/插件化。

何时选 :框架层、插件/脚手架、动态场景。


二、怎么选?------一张可落地的决策清单

  1. 普通业务类 → @Component(或语义化的 @Service/@Repository),配合构造器注入。
  2. 第三方对象/需要精细控制 (连接池、客户端、线程池)→ @Configuration + @Bean。
  3. 创建逻辑复杂/需要返回"产品而非工厂" → FactoryBean。
  4. 模块化/Starter/按条件成批装配 → @Import(ImportSelector/Registrar)+ @Conditional。
  5. 遗留或平台要求 → XML。
  6. 动态注册/插件化 → 运行时注册 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 提升启动性能。

四、常见踩坑与规避

  1. 同名/同类型冲突
  • 现象:NoUniqueBeanDefinitionException
  • 解法:约定 Bean 命名;@Qualifier 精确注入;需要默认实现时加 @Primary。
  1. 原型 Bean 生命周期失控
  • 容器只负责创建,不托管销毁;注入到单例里极易内存/状态泄漏。
  • 方案:ObjectProvider/Provider 延迟获取,或改成无状态。
  1. @Configuration 误用导致"重复实例化"
  • 在 Full 模式下通过代理保证同一 @Bean 单例复用;
  • 设 proxyBeanMethods=false 时跨方法互调会各自新建,需谨慎。
  1. FactoryBean 取到的是"产品不是工厂"
  • 想拿工厂本身要用 &beanName。这点面试常考。
  1. 条件/环境不生效
  • 激活 profile 写错;@ConditionalOnClass 等判断失败。
  • 启动参数/环境变量要对齐:spring.profiles.active=prod。
  1. 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;其余按场景增量选择。 选对方式,系统启动快、结构清晰、扩展成本更低。

相关推荐
kyriewen20 分钟前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒39 分钟前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫1 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus1 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
IT规划师2 小时前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试
孟陬3 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端
Zhao-Jichao3 小时前
AI Prompting for Everyone 课程笔记 - P17 图像生成 Imaging Generation
前端
Highcharts.js4 小时前
智能数据分析组件Orbit 正式发布与快速上手体验教程
前端·人工智能·信息可视化·数据分析系统·highcharts·orbit·智能可视化
Dream-Y.ocean4 小时前
鸿蒙PC平台星图遥感适配实战:基于 Electron 壳方案的纯 JavaScript 遥感图像处理引擎开发
javascript·electron·harmonyos
螺丝钉tina4 小时前
[Playwright ][16]Playwright + TypeScript API 自动化测试实战:API测试
前端·javascript·typescript