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

 更新时间:2025年10月18日 09:43:37   作者:渣哥  
本文详细梳理了Spring项目中Bean的六种常见创建方式(@Component、@Bean、FactoryBean、@Import、XML、运行时注册),对每种适用场景、优缺点与最佳实践进行了总结,并给出了选择方法的决策清单与常见误区规避建议,需要的朋友可以参考下

第一次接手老项目,我最懵的一件事是:同一个项目里,创建 Bean 的姿势能有五六种——@Component@BeanFactoryBean@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));

特点

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

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

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

  1. 普通业务类@Component(或语义化的 @Service/@Repository),配合构造器注入
  2. 第三方对象/需要精细控制(连接池、客户端、线程池)→ @Configuration + @Bean
  3. 创建逻辑复杂/需要返回“产品而非工厂”FactoryBean
  4. 模块化/Starter/按条件成批装配@ImportImportSelector/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;其余按场景增量选择。 选对方式,系统启动快、结构清晰、扩展成本更低。

以上就是Spring创建Bean的多种方式对比与最佳实践的详细内容,更多关于Spring创建Bean方式的资料请关注脚本之家其它相关文章!

相关文章

  • SWT(JFace) 简易浏览器 制作实现代码

    SWT(JFace) 简易浏览器 制作实现代码

    SWT(JFace) 简易浏览器 制作实现代码
    2009-06-06
  • Mybatis(ParameterType)传递多个不同类型的参数方式

    Mybatis(ParameterType)传递多个不同类型的参数方式

    这篇文章主要介绍了Mybatis(ParameterType)传递多个不同类型的参数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Spring Cloud Ribbon的使用原理解析

    Spring Cloud Ribbon的使用原理解析

    现在Java非常流行微服务,也就是所谓的面向服务开发,将一个项目拆分成了多个项目,其优点有很多,其中一个优点就是:将服务拆分成一个一个微服务后,我们很容易的来针对性的进行集群部署,这篇文章主要介绍了Spring Cloud Ribbon的使用详解,需要的朋友可以参考下
    2022-07-07
  • Java实现利用图片或视频生成GIF并发送微信

    Java实现利用图片或视频生成GIF并发送微信

    这篇文章主要为大家详细介绍了Java语言如何利用图片或视频实现生成GIF并发送微信的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以尝试一下
    2022-11-11
  • Mybatis入门教程之新增、更新、删除功能

    Mybatis入门教程之新增、更新、删除功能

    这篇文章给大家介绍了Mybatis进行基本的增删改操作,非常不错,具有参考借鉴价值,需要的的朋友参考下
    2017-02-02
  • 详谈spring中bean注入无效和new创建对象的区别

    详谈spring中bean注入无效和new创建对象的区别

    这篇文章主要介绍了spring中bean注入无效和new创建对象的区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java递归实现JSON全路径自动化探测工具

    Java递归实现JSON全路径自动化探测工具

    在日常开发中,面对嵌套十几层、成百上千行的复杂 JSON,开发者常面临以下困境:路径定位难,AI 幻觉问题,文档缺失等问题,本文分享一个轻量级的 Java 工具类,利用递归算法一键拉平 JSON,输出所有叶子节点的完整路径,需要的朋友可以参考下
    2026-02-02
  • 使用BufferedReader读取TXT文件中数值,并输出最大值

    使用BufferedReader读取TXT文件中数值,并输出最大值

    这篇文章主要介绍了使用BufferedReader读取TXT文件中数值,并输出最大值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java中synchronized正确使用方法解析

    Java中synchronized正确使用方法解析

    这篇文章主要介绍了Java中syncronized正确使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • mybatis插件pageHelper实现分页效果

    mybatis插件pageHelper实现分页效果

    这篇文章主要为大家详细介绍了mybatis插件pageHelper实现分页效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12

最新评论