通过 Java Config 来声明 Bean 是 Spring 中非常重要的一种方式,它与基于注解的组件扫描形成了互补。
如何通过 Java Config(@Configuration 和 @Bean)声明一个 Bean?
这种方式的核心是使用两个注解:@Configuration 和 @Bean。
-
@Configuration: 标记在一个类上,表明这个类是一个 Spring 配置类。它的作用相当于一个传统的 XML 配置文件。Spring 容器会处理这个类,并将其中的 Bean 定义加载进来。配置类本身也会被注册为一个 Bean。 -
@Bean: 标记在一个方法上,这个方法必须位于一个@Configuration类中。它告诉 Spring:“请将这个方法返回的对象注册为一个 Bean,并由你来管理”。
工作流程:
- 创建一个普通的 Java 类,并用
@Configuration标记它。 - 在这个类中,定义一个或多个方法。
- 在你想让 Spring 管理的方法上,加上
@Bean注解。 - 这个方法的返回值就是将被注册到 Spring 容器中的 Bean 实例。
- 默认情况下,这个 Bean 的名字就是方法名。
示例:
假设我们正在使用一个第三方库中的类,比如一个 JSON 解析库的 ObjectMapper。我们无法修改它的源代码,无法添加 @Component 注解。这时,Java Config 就是完美的解决方案。
1. 定义配置类 AppConfig.java
import com.fasterxml.jackson.databind.ObjectMapper; // 假设这是一个第三方类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 1. 声明这是一个配置类
public class AppConfig {
@Bean // 2. 声明这个方法返回的对象是一个Bean
public ObjectMapper objectMapper() { // 3. Bean的名字默认是 "objectMapper"
// 4. 这里是创建和配置Bean实例的逻辑
ObjectMapper mapper = new ObjectMapper();
// 可以在这里进行各种复杂的配置...
// mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper; // 5. 返回的对象将被放入Spring容器
}
}
2. 在业务代码中使用这个 Bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DataProcessingService {
private final ObjectMapper objectMapper; // 这是我们在AppConfig中定义的Bean
@Autowired
public DataProcessingService(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public String process(Object data) throws Exception {
// 使用被注入的 objectMapper Bean
return this.objectMapper.writeValueAsString(data);
}
}
当 Spring 容器启动时,它会加载 AppConfig,执行 objectMapper() 方法,将返回的 ObjectMapper 实例注册为一个名为 objectMapper 的 Bean。然后,当创建 DataProcessingService 时,Spring 会自动将这个 ObjectMapper Bean 注入进去。
这种方式和注解方式有什么不同,适用于什么场景?
这是一个非常关键的问题,理解它们的区别能帮助你写出更优雅、更合理的 Spring 应用。
| 特性 | 组件扫描 (@Component, @Service 等) | Java Config (@Bean) |
|---|---|---|
| 控制方 | 类本身决定自己是否是 Bean。 | 外部配置类决定一个对象是否是 Bean。 |
| 注解位置 | 直接在类上。 | 在返回 Bean 实例的方法上。 |
| 侵入性 | 侵入式:必须修改目标类的源代码来添加注解。 | 非侵入式:目标类可以是普通的 POJO,无需任何 Spring 注解。 |
| 灵活性 | 较低。Bean 的创建逻辑是固定的(通过构造函数实例化)。 | 极高。你可以在 @Bean 方法体内编写任意复杂的 Java 代码来创建和配置 Bean。 |
| 明确性 | 隐式。Bean 的定义分散在各个类中,由扫描机制自动发现。 | 显式。所有的 Bean 定义都集中在配置类中,一目了然。 |
适用场景分析:
什么时候使用组件扫描 (@Component, @Service, @Repository, @Controller)?
对于你自己编写的、属于你应用程序一部分的业务组件,组件扫描是最优选择。
- 你的业务逻辑类:比如
UserService,ProductRepository,OrderController等。 - 遵循“约定优于配置”:它让代码更简洁,减少了显式的配置代码。你只需在类上做一个标记,Spring 就会自动处理。
- 去中心化配置:Bean 的定义和它的实现代码放在一起,便于维护。
总结:用组件扫描来管理你自己应用中的类。
什么时候使用 Java Config (@Configuration, @Bean)?
当组件扫描不适用或不够灵活时,Java Config 就派上用场了。
-
管理第三方库的类
这是最经典、最常见的场景。你无法修改第三方库的源代码来添加@Component,所以必须使用@Bean方法来将其实例声明为一个 Bean。比如DataSource,RestTemplate,ObjectMapper,RedisTemplate等。 -
需要复杂初始化逻辑的 Bean
如果一个对象的创建过程很复杂,不仅仅是new一下那么简单,比如需要调用多个setter方法,或者使用构造者模式(Builder Pattern),那么@Bean方法体就是实现这些复杂逻辑的完美场所。@Bean public SomeComplexObject someComplexObject() { // 使用Builder模式创建对象 return new SomeComplexObject.Builder() .withPropertyA("valueA") .withPropertyB(123) .build(); } -
条件化创建 Bean
有时你希望只在满足特定条件时才创建某个 Bean。在@Bean方法上可以结合使用@Conditional...系列注解,实现非常精细的控制。@Bean @ConditionalOnProperty(name = "feature.toggle.enable-caching", havingValue = "true") public CacheManager redisCacheManager() { // 只有当配置文件中的 feature.toggle.enable-caching=true 时,这个Bean才会被创建 return new RedisCacheManager(...); } -
将一个类的多个不同配置实例注册为 Bean
假设你需要两个功能不同但类型相同的RestTemplateBean,一个用于调用内部服务(超时短),一个用于调用外部服务(超时长)。组件扫描无法做到这一点,但@Bean可以轻松实现。@Bean public RestTemplate internalRestTemplate() { // ... 配置一个短超时的RestTemplate return new RestTemplate(...); } @Bean public RestTemplate externalRestTemplate() { // ... 配置一个长超时的RestTemplate return new RestTemplate(...); }
最终总结
@Component及其衍生注解:是“自荐模式”。类自己说:“我是个 Bean,快来管我!”。适用于应用内部的、由你控制的组件。@Configuration+@Bean:是“举荐模式”。配置类说:“我推荐这个方法返回的对象做个 Bean,你来管管它!”。适用于第三方类、复杂创建逻辑或需要精细化控制的场景。
在实际开发中,这两种方式通常会混合使用,相得益彰,共同构成了 Spring 强大而灵活的配置模型。

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



