SpringBoot26-@Configuration + @Component
2026/7/25 5:56:42 网站建设 项目流程

一、@Configuration配置类

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

1. 核心概念与本质

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

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

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

@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 进行注入。

@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= ...)调节容器行为。

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

Full 模式 vs Lite 模式

特性Full 模式 (true,默认)Lite 模式 (false)
原理使用CGLIB为配置类生成动态代理类不生成代理类,仅作为普通类处理
方法间互调在 Bean 方法内部调用另一个@Bean方法时,总是从容器获取单例每次调用都会直接执行普通方法,产生新的实例
启动性能略慢(需生成 CGLIB 代理)更快,减少内存与启动消耗
适用场景组件间存在内部依赖关系(组件 A 依赖组件 B 的单例)仅声明组件,组件之间无内部依赖调用
代码对比说明:
@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:配置文件中配置了指定属性时生效。

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

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

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

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

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

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

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

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

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

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

方式1(推荐)
// 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:
// 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,你就不想加载跟短信相关的任何类以节省内存。

@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)选择性加载配置类。

@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 模式 - 会出问题)
@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 模式 - 安全)
@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写法:

@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出来的效果是一样的,而且更轻量。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询