并非所有需要纳入 Spring 容器的类都允许我们直接在上面添加 @Component 或 @Service 注解。在实际开发中,大量依赖来自外部 JAR 包——例如数据源 HikariDataSource、线程池 ThreadPoolExecutor、Spring Security 的 PasswordEncoder 实现类、Redis 的 JedisConnectionFactory 等。这些类的源码不受我们控制,无法直接修改。此时,@Configuration + @Bean 就成为将第三方类注册为 Spring Bean 的标准方式。
3.2.1 基本用法
在任意一个用 @Configuration 注解的类中,编写返回目标类型实例的方法,并为该方法添加 @Bean 注解。Spring 容器会在启动时调用这些方法,并将返回的对象纳入容器管理。
@Configuration
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
// BCryptPasswordEncoder 是 Spring Security 提供的类,无法在上面加 @Component
return new BCryptPasswordEncoder();
}
}
之后,在应用的任何地方都可以像注入普通 Bean 一样使用它:
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
// 容器自动注入通过 @Bean 注册的实例
public UserService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
}
3.2.2 何时使用 @Bean 注册
关键原则:当源代码不可修改(第三方库)或者创建过程比较复杂、需要显式控制参数时,采用 @Bean 方式;自己编写的业务类优先使用 @Component 等注解,代码更简洁。
典型适用场景:
- 配置数据源:
HikariDataSource、DruidDataSource等连接池。 - 注册安全组件:
PasswordEncoder、SecurityFilterChain、JwtDecoder等。 - 创建线程池与调度器:
ThreadPoolExecutor、ScheduledExecutorService。 - 客户端与连接工厂:
RestClient、RedisConnectionFactory、KafkaTemplate。 - 需要自定义初始化逻辑的对象:例如设置参数、调用 setter 方法后才完整可用的 Bean。
- 同一类型有多个实例需要区分:例如配置两个不同策略的
ObjectMapper。
3.2.3 利用参数自动注入简化配置
@Bean 方法可以有参数,Spring 会按类型自动从容器中寻找匹配的依赖并注入,这与构造器注入的工作原理完全相同。这让我们可以轻松组合多个第三方组件。
@Configuration
public class CacheConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
return new JedisConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) { // 自动注入上面定义的 Bean
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
3.2.4 控制 Bean 的生命周期
第三方类经常需要初始化和销毁操作——比如连接池的预热、线程池的关闭。Spring 提供了两种方式在不侵入源码的前提下控制生命周期。
1. initMethod / destroyMethod 属性
直接在 @Bean 注解中指定初始化与销毁回调方法名:
@Bean(initMethod = "start", destroyMethod = "shutdown")
public SomeThirdPartyService someService() {
return new SomeThirdPartyService();
}
Spring 会在 Bean 实例化后自动调用 start() 方法,容器关闭前自动调用 shutdown() 方法。对于数据源等常用组件,Spring 还能自动推断销毁方法(如 close()、shutdown()),一般无需显式指定。
2. 直接在 @Bean 方法内部编程处理
如果需要在创建过程中直接调用初始化逻辑,也可以在方法体中手动调用:
@Bean
public Scheduler scheduler() {
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start(); // 容器启动阶段即完成初始化
return scheduler;
}
3.2.5 多实例与命名
当同一类型存在多个 Bean 时,可以通过 @Bean 的 name 属性或方法名明确标识。注入时结合 @Qualifier 或 @Resource 按名称区分。
@Configuration
public class ObjectMapperConfig {
@Bean("defaultMapper")
public ObjectMapper defaultObjectMapper() {
return new ObjectMapper();
}
@Bean("prettyMapper")
public ObjectMapper prettyObjectMapper() {
return new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
}
}
注入时:
@Service
public class ReportService {
private final ObjectMapper mapper;
public ReportService(@Qualifier("prettyMapper") ObjectMapper mapper) {
this.mapper = mapper;
}
}
3.2.6 与 @Component 的差异总结
| 方式 | 适用对象 | 特点 |
|------|---------|------|
| @Component / @Service 等 | 自己编写的业务类 | 源码可控,直接在类上标注,容器自动扫描创建单例 |
| @Bean | 第三方类、复杂构造对象 | 源码不可修改;通过方法体显式控制创建逻辑,灵活配置参数和生命周期 |
两者并不互斥,在同一个项目中共存是常态。自己开发的 Service 层使用 @Service 标注,同时通过 @Configuration 暴露各种基础组件 Bean,容器统一管理所有实例,这就是 Spring 应用的典型组织方式。