你每天都在用,却未必说清「为什么是三种」
写 Spring 的人,几乎天天在「把一个对象交给容器」。@Service 标一下、配置类里 @Bean 写一个、或者 @Configuration 丢一堆方法------但它们本质不是一回事。
很多开发者能把三种写法都写出来,一被问「@Bean 放在 @Component 和 @Configuration 里到底差在哪」「为什么有的 @Bean 互相调用拿的是同一个实例、有的却 new 了好几个」,就卡壳了。这一篇把三种注册方式的身世、边界,以及那个最容易被忽略的 full/lite 玄机一次讲透。
三种方式,按「容器怎么知道这个 Bean 该被注册」来区分:
- 方式一:组件扫描( Stereotype +
@ComponentScan)------类上贴注解,容器扫包自动认。 - 方式二:
@Bean方法 ------你在方法里new,告诉容器「按这个方法造」。 - 方式三:
@Configuration配置类------方式二的家,但它是「带了超能力的家」。
下面逐一拆,重点在第三种。
方式一:组件扫描,最省事的「贴标签」
@Component 是 Spring 对所有托管组件的通用原型(stereotype) 。而 @Repository、@Service、@Controller 只是它在持久层、服务层、表现层的特化(specializations) ------它们身上都元注解了 @Component ,所以容器看待它们的姿势完全一致,区别只在语义和额外能力(比如 @Repository 会自动做持久层异常翻译)。(出处:官方 · @Component and Further Stereotype Annotations)
光贴 @Service 还不够,容器得知道「去哪扫」。这就是 @ComponentScan 的活:
java
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig { }
一个 @ComponentScan,容器就会去 com.example 下把贴了 @Component/@Service/@Repository/@Controller/@Configuration 的类自动探测并注册成 BeanDefinition。(出处:官方 · Automatically Detecting Classes and Registering Bean Definitions)
几个实战要点:
- 默认 bean 名 :非限定类名首字母小写,比如
UserService→userService,也可用@Component("xxx")显式指定。 - 默认作用域 :
singleton,可用@Scope改。 - 过滤 :
@ComponentScan(includeFilters=..., excludeFilters=...)能按注解 / 类型 / 正则 / AspectJ 自定义哪些扫、哪些不扫;useDefaultFilters=false还能关掉默认那套注解探测。
一句话:组件扫描适合「自己写、自己管的业务类」------贴个注解就完事,零配置心智负担。
方式二:@Bean,把「怎么造」写进方法
@Bean 是方法级 注解,角色等价于 XML 里的 <bean/> 元素:你在一个方法里 new 出对象并配置好,方法返回类型就是 Bean 的类型,默认 bean 名就是方法名。(出处:官方 · Using the @Bean Annotation)
java
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
这里有两个要知道的点:
- 依赖通过方法参数表达 :
transferService(AccountRepository ...)的依赖解析,和构造器注入是同一套机制------容器先找好AccountRepository,再调这方法。这就是为什么「@Bean方法互相调用」其实不是表达依赖的正道,正道是写参数。 - 生命周期回调 :
@Bean(initMethod = "init", destroyMethod = "cleanup")等价于 XML 的init-method/destroy-method;而且容器会自动给有 publicclose/shutdown方法的 Bean 挂上销毁回调 (太贴心也容易踩雷------比如 JNDI 拿的DataSource就不是你该关的,得@Bean(destroyMethod = "")显式关掉默认推断)。
注意:官方明确说
@Bean既可以用在@Configuration里,也可以用在任何@Component里。这正是后面 full/lite 玄机的入口。
方式三:@Configuration,方式二真正的「家」
@Configuration 的首要目的就是当 bean 定义源 。它自己也被 @Component 元注解了------所以配置类本身也是一个 Bean,而且是组件扫描的默认候选之一 (和 @Component/@Service 平起平坐)。(出处:官方 · Using Filters to Customize Scanning)
也就是说,你在 @Configuration 类里写的每个 @Bean 方法,都是在向容器「申报」一个 Bean 定义。@Configuration 和 @Component 在这里的差别,不在「能不能写 @Bean」,而在下面的 full/lite 模式。
重点:full 模式为何要 CGLIB 代理(lite 的反面)
这是 @Bean 放在 @Configuration vs @Component 里唯一、也是最要命的区别。(出处:官方 · Basic Concepts: @Bean and @Configuration)
full 模式(默认) :@Configuration 类在启动时,Spring 会用 CGLIB 生成一个子类代理 ,覆写里面的 @Bean 方法。当你在配置类里写 service() 调 repository(),调用会被代理拦截 ------代理先去容器里查有没有名为 repository 的 Bean,有就直接返回那个单例,没有才真去执行方法体并注册 。于是无论你调多少次 repository(),拿到的永远是同一个容器托管实例。
官方给的理由很直白:这样能防止同一个 @Bean 方法被普通 Java 调用意外执行多次、造出多个实例这类难以排查的隐患。
lite 模式 :当 @Bean 方法写在非 @Configuration 的类 (比如 @Component)里,或显式标了 @Configuration(proxyBeanMethods = false) 时,就进入 lite 模式------没有 CGLIB 代理 ,方法调用就是普通 Java 调用 :service() 调 repository(),每次都是 new 一个新对象,而不是复用容器里的单例。
后果很明确:lite 模式下别用「方法互相调用」来表达 Bean 间依赖 ,因为那根本不会被容器托管。正确姿势是把依赖写成方法参数(前面方式二提过),让容器通过参数注入。lite 模式下的 @Bean 更像一把通用工厂方法,不享受跨方法引用重定向。
深度钩子:Spring Boot 的
@SpringBootApplication(经@SpringBootConfiguration)默认就是@Configuration(proxyBeanMethods = false),即 lite 模式 。所以 Boot 应用里你写的@Bean互相调用大概率不会复用单例------这是无数「我明明配了单例却有多个实例」惨案的源头。这条是 Spring Boot 的行为,不是 Framework 参考文档里的通用约定。
收益权衡:lite 模式省掉了运行期 CGLIB 子类化,启动开销和内存占用更小,这也是 Spring Boot 默认选它的原因。代价就是你得自己管住「别在配置类里用方法调用串依赖」。
看图就懂:full vs lite 的调用链路

左边 full 模式:调用链 service() → repository() 全程经过代理,最终都指向容器里那个唯一的 Repository 单例(图中只实例化一次)。
右边 lite 模式:service() 直接 new 出一个 Repository、repository() 又被 new 一次,两个互不相干的实例------依赖没被容器托管,埋下隐患。
到底怎么选三种方式
- 业务类、自己写的 :
@Component/@Service/@Repository+ 组件扫描。零样板,最顺手。 - 造第三方 / 复杂对象(连接池、客户端、需要 init/destroy 的) :
@Bean。你能 100% 控制new和配置过程。 @Configuration里的@Bean:默认 full 模式,适合需要「@Bean方法互相引用、保证单例复用」的场景;如果你确定不会在配置类里串方法调用依赖,显式proxyBeanMethods = false切 lite 模式省开销(Spring Boot 默认就这么做)。
一句话收束:组件扫描管「贴标签的业务类」,@Bean 管「怎么造难造的对象」,@Configuration 是 @Bean 的家,而 full/lite 模式决定了这个家里的方法调用到底走不走容器。 多数踩坑,都栽在后半句。
📌 版本与出处说明 :本文基于 Spring Framework 7.x 官方 Reference(查阅时文档版本 7.0.10,2026-10)撰写,核心概念均对照官方文档提炼,正文关键处标注了对应官方小节。官方文档若改版,以官网最新版为准。