人人都会AI编程

@Configuration + @Bean 注册第三方类

更新时间:2026-07-11

并非所有需要纳入 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 等注解,代码更简洁。

典型适用场景:

  • 配置数据源HikariDataSourceDruidDataSource 等连接池。
  • 注册安全组件PasswordEncoderSecurityFilterChainJwtDecoder 等。
  • 创建线程池与调度器ThreadPoolExecutorScheduledExecutorService
  • 客户端与连接工厂RestClientRedisConnectionFactoryKafkaTemplate
  • 需要自定义初始化逻辑的对象:例如设置参数、调用 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 时,可以通过 @Beanname 属性或方法名明确标识。注入时结合 @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 应用的典型组织方式。