简单来说:
@Component是一个通用的构造型注解,它告诉 Spring:“请把我这个类扫描成一个 Bean”。@Configuration是一个特殊的@Component。它不仅能让类成为 Bean,还开启了一项“魔法”功能:确保@Bean方法返回的都是由容器管理的单例 Bean。
快速对比
| 特性 | @Component | @Configuration |
|---|---|---|
| 本质 | 通用 Bean 标记 | 特殊的 Bean 标记 |
| 继承关系 | 无 | @Configuration 自身被 @Component 元注解标注 |
| 核心目的 | 标记一个普通的业务组件(Service, Repository等) | 专门用于定义 Bean 的配置类 |
@Bean 方法行为 | Lite Mode(轻量模式) | Full Mode(完整模式) |
| 背后原理 | 普通的 Bean | 通过 CGLIB 代理增强 |
关键区别:@Bean 方法的行为
这个区别是理解两者的关键。让我们通过一个例子来揭示这个“魔法”。
场景:我们有一个 Car Bean,它依赖于一个 Engine Bean。我们希望整个应用中只有一个 Engine 实例。
1. 使用 @Configuration(正确的方式)
@Configuration
public class AppConfig {
@Bean
public Engine engine() {
System.out.println(">>> 创建 Engine Bean...");
return new Engine();
}
@Bean
public Car car() {
System.out.println(">>> 创建 Car Bean...");
// 这里直接调用了 engine() 方法
Engine carEngine = this.engine();
return new Car(carEngine);
}
}
运行结果:
>>> 创建 Engine Bean...
>>> 创建 Car Bean...
分析:
engine()方法只被调用了一次!- 当我们创建
carBean 并调用this.engine()时,Spring 并没有真的去执行engine()方法体里的new Engine()。 - 为什么? 因为
AppConfig类被@Configuration标记了,Spring 在启动时会为它创建一个 CGLIB 代理子类(比如AppConfig$$EnhancerBySpringCGLIB$$...)。 - 这个代理类重写了所有的
@Bean方法。当你调用this.engine()时,代理会拦截这个调用,并检查 IoC 容器中是否已经存在一个名为 “engine” 的 Bean。如果存在,就直接返回容器中的那个单例实例;如果不存在,才执行原始方法体来创建它。 - 结论:
@Configuration保证了无论你调用@Bean方法多少次,返回的都是同一个由容器管理的单例对象。
2. 使用 @Component(错误或危险的方式)
现在,我们把 @Configuration 换成 @Component,其他代码完全不变。
@Component // 这里换成了 @Component
public class AppConfigLite {
@Bean
public Engine engine() {
System.out.println(">>> 创建 Engine Bean...");
return new Engine();
}
@Bean
public Car car() {
System.out.println(">>> 创建 Car Bean...");
// 这里直接调用了 engine() 方法
Engine carEngine = this.engine();
return new Car(carEngine);
}
}
运行结果:
>>> 创建 Engine Bean...
>>> 创建 Car Bean...
>>> 创建 Engine Bean...
分析:
engine()方法被调用了两次!- 为什么? 因为
AppConfigLite被@Component标记,Spring 只把它当作一个普通的 Bean。它不会为这个类创建 CGLIB 代理。 - 因此,当
car()方法内部调用this.engine()时,它就是一个普通的 Java 方法调用。它会完整地执行engine()方法体,即new Engine(),从而创建了一个全新的、不受 Spring 容器管理的Engine实例。 - 结论:在
@Component类中调用@Bean方法,会绕过容器的管理,导致每次都创建新对象,破坏了单例作用域。
为什么有些配置类要用 @Configuration?
答案就是为了解决上面例子中的问题:确保 Bean 之间的依赖关系正确无误。
一个经典的真实场景是 DataSource 和 JdbcTemplate:
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
// 创建并返回一个 DataSource Bean
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("...");
ds.setUsername("...");
ds.setPassword("...");
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate() {
// JdbcTemplate 依赖于 DataSource
// 我们希望它使用的是上面由容器管理的那个单例 DataSource
return new JdbcTemplate(this.dataSource());
}
@Bean
public MyTransactionManager myTransactionManager() {
// 另一个组件也依赖于同一个 DataSource
return new MyTransactionManager(this.dataSource());
}
}
在这个例子中,jdbcTemplate() 和 myTransactionManager() 都需要 DataSource。因为我们用了 @Configuration,所以两次 this.dataSource() 调用都会被 Spring 的代理拦截,并返回同一个由容器管理的 DataSource 实例。
如果这里错误地使用了 @Component,那么 JdbcTemplate 和 MyTransactionManager 将会各自持有一个全新的、独立的、不受容器管理的 DataSource 实例,这可能会导致连接池失效、事务管理混乱等一系列灾难性问题。
总结
@Configuration (Full Mode) | @Component (Lite Mode) | |
|---|---|---|
| 用途 | 首选,用于定义Bean的配置类,特别是当Bean之间有依赖关系时。 | 用于普通的业务组件。不推荐在其中定义有相互依赖的 @Bean 方法。 |
| 行为 | 通过CGLIB代理,保证 @Bean 方法调用返回容器中的单例Bean。 | 普通Java方法调用,每次都创建新对象,绕过容器管理。 |
| 何时使用 | 只要你的配置类里有 @Bean 方法,特别是它们之间有调用关系,就必须使用 @Configuration。 | 当你的类只是一个简单的业务组件(如Service),或者你的配置类里的 @Bean 方法完全独立,没有相互调用时(但依然不推荐)。 |
所以,请记住这个黄金法则:任何用于定义 Bean(即包含 @Bean 方法)的类,都应该使用 @Configuration 注解,以确保其行为符合 Spring IoC 容器的预期。
1266

被折叠的 条评论
为什么被折叠?



