SpringBoot26-@Configuration + @Component

一、@Configuration配置类

在 Spring Boot 中,@Configuration(配置类) 是替代传统 Spring XML 配置文件(如 applicationContext.xml)的核心手段。它采用 Java Config 的方式,将对象的创建、组装和依赖注入逻辑用 Java 代码清晰地表达出来。

1. 核心概念与本质

@Configuration 声明在类上,告诉 Spring 容器:"这个类是一个 Bean 对象的生产工厂,里面包含了组件注册与配置的蓝图。"

java 复制代码
@Configuration
public class AppConfig {
    // 容器会读取这个类,并将其内部 @Bean 方法返回的对象注册到 IoC 容器中
}

本质: @Configuration 底层继承了 @Component 注解。这意味着配置类本身也是一个被 Spring 容器管理的 Bean

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Configuration {

}

2. 声明与注册 Bean(@Bean 的用法)

在配置类内部,最核心的用法是结合 @Bean 注解来声明组件。

  • @Component 作用于类 :告诉 Spring,"这个类 是我写的,请帮我实例化它并放进容器。"

  • @Bean 作用于方法 :告诉 Spring,"请执行这个方法 ,并把方法返回的对象放进容器。"


基础定义与参数自动注入

@Bean 方法中,方法的返回值 会作为注册到容器中的 Bean 实例,方法名默认作为 Bean 的唯一标识(Bean ID)。

如果方法带有参数,Spring 会自动从容器中查找匹配的 Bean 进行注入。

java 复制代码
@Configuration
public class DatabaseConfig {

    // 声明一个 DataSource 类型的 Bean,Bean ID 默认为 "dataSource"
    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        ds.setUsername("root");
        ds.setPassword("secret");
        return ds;
    }

    // 方法参数 (DataSource ds) 会由 Spring 容器自动注入上面定义的 dataSource
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource ds) {
        return new JdbcTemplate(ds);
    }
}

属性与生命周期控制

通过辅助注解,可以精准控制 Bean 的行为:

注解 / 参数 描述 示例
@Bean(name = "...") 自定义 Bean 的名称(可指定多个别名) @Bean(name = {"myService", "aliasService"})
@Scope 作用域:singleton(单例,默认)或 prototype(原型/多例) @Scope("prototype")
@Primary 存在多个同类型 Bean 时,优先注入该 Bean @Primary
@Lazy 延迟初始化(首次使用时才创建 Bean,而非启动时) @Lazy
initMethod / destroyMethod 指定 Bean 的初始化方法和销毁钩子方法 @Bean(initMethod = "init", destroyMethod = "close")

3. 两种代理模式:proxyBeanMethods (Full 与 Lite 模式)

这是 @Configuration 最底层也最重要的机制之一。

可以通过 @Configuration(proxyBeanMethods = ...) 调节容器行为。

java 复制代码
// 默认为 true (Full 模式)
@Configuration(proxyBeanMethods = true)
public class FullConfig {
    // ...
}

Full 模式 vs Lite 模式

特性 Full 模式 (true,默认) Lite 模式 (false)
原理 使用 CGLIB 为配置类生成动态代理类 不生成代理类,仅作为普通类处理
方法间互调 在 Bean 方法内部调用另一个 @Bean 方法时,总是从容器获取单例 每次调用都会直接执行普通方法,产生新的实例
启动性能 略慢(需生成 CGLIB 代理) 更快,减少内存与启动消耗
适用场景 组件间存在内部依赖关系(组件 A 依赖组件 B 的单例) 仅声明组件,组件之间无内部依赖调用
代码对比说明:
java 复制代码
@Configuration(proxyBeanMethods = true) // Full 模式
public class AppConfig {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        // 直接调用 userDao() 方法,Spring 代理拦截该方法,直接从 IoC 容器拿单例,绝不会 new 两次!
        return new UserService(userDao()); 
    }
}

4. 条件装配(Conditional Configuration)

Spring Boot 的灵魂在于按需加载

结合 @ConditionalOn* 系列注解,可以让配置类或某个 @Bean 仅在满足特定条件时才生效。


常用条件注解汇总

  • @ConditionalOnProperty:配置文件中配置了指定属性时生效。

    java 复制代码
    @Bean
    @ConditionalOnProperty(name = "feature.email.enabled", havingValue = "true")
    public EmailService emailService() {
        return new EmailService();
    }
  • @ConditionalOnClass / @ConditionalOnMissingClass :类路径(Classpath)下存在/不存在指定类时生效。

  • @ConditionalOnBean / @ConditionalOnMissingBean :容器中存在/不存在指定 Bean 时生效(非常适合做默认保底配置)。

    java 复制代码
    @Bean
    @ConditionalOnMissingBean(SearchService.class)
    public SearchService defaultSearchService() {
        return new DefaultSearchServiceImpl(); // 用户没配就用默认的
    }

5. 配置类的模块化与组合

为了避免单一配置类过于庞大,通常需要拆分并进行组合。

1. @Import(引入外部配置或类)

用于直接将其他配置类、普通组件类、或者动态注册器注入容器。

java 复制代码
@Configuration
@Import({SecurityConfig.class, CacheConfig.class}) // 组合其他配置类
public class MainConfig {
}

2. @EnableConfigurationProperties(结合配置绑定)

application.yml 中的属性映射到实体类(@ConfigurationProperties),并在配置类中开启和使用。

方式1(推荐)
java 复制代码
// 1. 定义配置属性类
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    private String apiKey;
    private String secret;
    // getters and setters...
}

// 2. 在配置类中启用绑定并使用
@Configuration
@EnableConfigurationProperties(SmsProperties.class)
public class SmsConfig {

    @Bean
    public SmsClient smsClient(SmsProperties properties) {
        return new SmsClient(properties.getApiKey(), properties.getSecret());
    }
}
方式2:
java 复制代码
// 1. 定义配置属性类
@Component
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    private String apiKey;
    private String secret;
    // getters and setters...
}

// 2. 在配置类中启用绑定并使用
@Configuration
public class SmsConfig {

    @Bean
    public SmsClient smsClient(SmsProperties properties) {
        return new SmsClient(properties.getApiKey(), properties.getSecret());
    }
}
这两种方式的区别:

方式2的这种方式(直接加 @Component)完全可以,并且在日常的业务代码开发中非常常见! 代码不仅能跑通,而且在单一业务系统里也是一种很便捷的写法。

既然方式2的写法能跑通,为什么 Spring Boot 官方还要搞一个 @EnableConfigurationProperties 呢?

这主要涉及到 组件封装设计原则条件触发 以及 开发第三方框架(Starter) 时的场景限制。

下面我为你详细梳理这两种方式的适用场景和底层设计考量:


(1). 为什么官方推荐用 @EnableConfigurationProperties
场景一:开发第三方 Starter(最核心的原因)

如果你在写一个给别人用的 sms-spring-boot-starter,你的包名可能是 com.yourcompany.sms,而使用者的主程序包名是 com.user.app

  • 如果你用 @Component 使用者的 Spring Boot 默认只会扫描 com.user.app 目录下的组件。你的 SmsProperties 带有 @Component不会被扫描到,导致注入失败。

  • 如果你用 @EnableConfigurationProperties(SmsProperties.class) 你可以在你的自动配置类(SmsAutoConfiguration)上使用这个注解。只要自动配置类被加载,它就会强制SmsProperties 注册为 Bean。这种方式完全摆脱了 @ComponentScan(包扫描)的限制

场景二:做到真正的"按需加载"与条件装配

在底层框架设计中,通常希望组件是完全解耦且按需加载的。

假设用户没有在 application.yml 里配置 sms.enabled=true,你就不想加载跟短信相关的任何类以节省内存。

java 复制代码
@Configuration
@ConditionalOnProperty(name = "sms.enabled", havingValue = "true")
@EnableConfigurationProperties(SmsProperties.class)
public class SmsConfig {
    // 只有在 sms.enabled=true 时,SmsConfig 才会生效。
    // SmsConfig 生效了,SmsProperties 才会被注册为 Bean。
}

如果你在 SmsProperties 上直接加了 @Component,那么无论有没有开启短信功能,Spring 启动时都会扫描并无脑实例化这个配置类对象,这就违背了 Spring Boot "按需加载"的优雅设计。

场景三:保持 POJO 的纯粹性(解耦)

SmsProperties 本质上只是一个装载数据的 POJO(纯 Java 对象)。从架构设计的洁癖角度来看,把它打上 @Component 的烙印,就意味着它和 Spring 的 IoC 容器深度绑定了。使用 @EnableConfigurationProperties 可以让实体类只保留 @ConfigurationProperties(纯粹声明属性前缀),由配置类去决定何时将它纳入 Spring 容器。

(2). 总结与最佳实践

其实,随着 Spring Boot 的演进,现在处理配置绑定有三种流派,可以根据你的实际场景来选择:

方式 写法特征 适用场景
方式 1:包扫描(你的写法) @Component + @ConfigurationProperties 普通业务模块。自己项目里随便写,简单粗暴,直接注入。
方式 2:显式启用(官方经典) @ConfigurationProperties + 配置类上的 @EnableConfigurationProperties 开发通用组件 / Starter 。需要规避包扫描限制,或者需要与其他 @Conditional 条件联合按需加载。
方式 3:配置树扫描(Spring Boot 2.2+ 新特性) POJO 上只写 @ConfigurationProperties,然后在启动类加上 @ConfigurationPropertiesScan 大型业务系统 。统一管理所有配置类,既不用写 @Component,也不用在每个配置类上写 Enable 注解。

3. @Profile(多环境隔离)

根据当前激活的 Profile(如 devtestprod)选择性加载配置类。

java 复制代码
@Configuration
@Profile("dev") // 仅在 spring.profiles.active=dev 时生效
public class DevDatabaseConfig {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder().build(); // 使用内存数据库
    }
}

6. Spring Boot 自动配置原理扩展

当你在编写可复用的 Starter 模块时,配置类的注册机制有所不同:

  1. Spring Boot 2.7 前 :在 META-INF/spring.factories 中配置配置类。

  2. Spring Boot 3.x 及以上 :在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中按行写入配置类的全路径名。

  3. 使用 @AutoConfiguration(Spring Boot 2.7+ 引入)替代标准的 @Configuration 作为自动配置类的入口,配合 @AutoConfigureBefore / @AutoConfigureAfter 控制加载顺序。

7. 最佳实践清单

  1. 单功能原则 :按业务模块拆分配置类(如 RedisConfigSecurityConfigSwaggerConfig),避免产生"超级配置类"。

  2. 善用 proxyBeanMethods = false :若 @Bean 方法之间不需要互相调用来保证单例,建议设置为 false 提升系统启动速度。

  3. 优先使用参数注入 :在 @Bean 方法定义中,直接将依赖写入方法参数,利用 Spring 自动装配,减少定义不必要的内部字段。

  4. 合理使用 @ConditionalOnMissingBean:在封装公共 SDK/组件时,始终用该注解留出拓展口,方便业务侧自定义覆盖。


二、@Component + @Bean的组合

@Component 类中使用 @Bean,和在 @Configuration 类中使用 @Bean,底层有着非常核心且致命的区别

还记得我们在第一讲提到的 "Full 模式""Lite 模式" 吗?这就是它们最大的差异。


核心区别:是否生成代理对象保证单例

  • @Configuration + @Bean(Full 模式): Spring 会使用 CGLIB 给这个配置类生成一个代理子类。当你在一个 @Bean 方法中调用另一个 @Bean 方法时,代理对象会拦截这个调用,去 Spring 容器里拿单例对象。

  • @Component + @Bean(Lite 模式): Spring 不会 生成代理类。类就是一个普通的 Java 类。如果你在内部发生方法互调,就是纯粹的 Java 方法执行,会每次都 new 出一个全新的对象,从而破坏 Spring 默认的单例机制!

代码对比:

假设我们有两个 Bean:UserDaoUserServiceUserService 依赖 UserDao)。

场景 1:使用 @Component(Lite 模式 - 会出问题)
java 复制代码
@Component
public class AppComponents {

    @Bean
    public UserDao userDao() {
        System.out.println("UserDao 被创建了!");
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        // 【注意这里】直接调用了 userDao() 方法
        return new UserService(userDao());
    }
}

其实这个方法,idea层就会编译报错了!

运行结果 :控制台会打印 两次 "UserDao 被创建了!"

  1. Spring 扫描到 @Bean userDao(),调用一次,注册到容器。

  2. Spring 扫描到 @Bean userService(),执行内部的 userDao() 方法,由于没有代理拦截,这就是普通 Java 方法调用,又 new 了一个全新的 UserDao

    后果UserService 里注入的 UserDao,和 Spring 容器里管理的那个 UserDao不是同一个对象

场景 2:使用 @Configuration(Full 模式 - 安全)
java 复制代码
@Configuration
public class AppConfig {

    @Bean
    public UserDao userDao() {
        System.out.println("UserDao 被创建了!");
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        // 这里的 userDao() 调用会被 Spring 拦截
        return new UserService(userDao());
    }
}

运行结果 :控制台只打印 一次 "UserDao 被创建了!"

在执行 userService() 时,Spring 的代理类发现你要调用 userDao(),它会说:"等一下,容器里已经有这个单例了,我直接把容器里的给你,别再 new 了。"

那么,什么时候可以用 @Component + @Bean

如果你定义的多个 @Bean 之间完全独立,不需要互相调用,那么使用 @Component + @Bean 是完全没问题的,甚至更好!

因为不需要生成 CGLIB 代理,Spring 的启动速度会稍微快一点,内存消耗也少一点。这种写法在 Spring 源码内部(比如一些自动配置类)经常被使用。

为了避免掉坑,最佳实践建议:

如果你在 @Component 中定义 @Bean,遇到需要依赖其他 Bean 的情况,坚决不要用方法调用,而是用参数注入


正确的 @Component + @Bean 写法:

java 复制代码
@Component
public class AppComponents {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    // 通过参数 userDao 让 Spring 自动注入,而不是去调用 userDao() 方法
    @Bean
    public UserService userService(UserDao userDao) {
        return new UserService(userDao);
    }
}

总结: @Component 确实可以和 @Bean 一起用(Lite 模式)。

只要你不通过"直接调用方法"的方式来注入依赖,它和 @Configuration 出来的效果是一样的,而且更轻量。

相关推荐
霸道流氓气质2 小时前
SpringBoot中使用JasperReports 报表引擎 — 介绍、原理与使用实践
java·spring boot·后端
风景的人生3 小时前
流式输出与springboot中的响应式编程
java·spring boot·ai编程
dkbnull4 小时前
Spring Boot依赖注入方式对比详解
spring boot·spring
__log4 小时前
幂等性设计:从“重复提交“到“稳如磐石“的系统防护
java·开发语言·spring boot
zhangjw344 小时前
第35篇:Spring Boot入门:自动配置+快速搭建,简化企业开发
java·spring boot·后端
Java内核笔记20 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
海棠Flower未眠1 天前
SpringBoot 消息死信队列(荣耀典藏版)
java·数据库·spring boot
腾渊信息科技公司1 天前
Spring Boot + TDengine:工业视觉检测数据实时同步方案实战
spring boot·后端·tdengine
weixin_BYSJ19871 天前
「课设设计」springboot校园超市助购系统26449 (附源码)
java·javascript·spring boot·python·django·flask·php