一、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. 声明与注册 BeanBean的用法在配置类内部最核心的用法是结合Bean注解来声明组件。Component作用于类告诉 Spring“这个类是我写的请帮我实例化它并放进容器。”Bean作用于方法告诉 Spring“请执行这个方法并把方法返回的对象放进容器。”基础定义与参数自动注入在Bean方法中方法的返回值会作为注册到容器中的 Bean 实例方法名默认作为 Bean 的唯一标识Bean ID。如果方法带有参数Spring 会自动从容器中查找匹配的 Bean 进行注入。Configuration public class DatabaseConfig { // 声明一个 DataSource 类型的 BeanBean 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 时优先注入该 BeanPrimaryLazy延迟初始化首次使用时才创建 Bean而非启动时LazyinitMethod / 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 ConfigurationSpring 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.enabledtrue你就不想加载跟短信相关的任何类以节省内存。Configuration ConditionalOnProperty(name sms.enabled, havingValue true) EnableConfigurationProperties(SmsProperties.class) public class SmsConfig { // 只有在 sms.enabledtrue 时SmsConfig 才会生效。 // SmsConfig 生效了SmsProperties 才会被注册为 Bean。 }如果你在SmsProperties上直接加了Component那么无论有没有开启短信功能Spring 启动时都会扫描并无脑实例化这个配置类对象这就违背了 Spring Boot “按需加载”的优雅设计。场景三保持 POJO 的纯粹性解耦SmsProperties本质上只是一个装载数据的 POJO纯 Java 对象。从架构设计的洁癖角度来看把它打上Component的烙印就意味着它和 Spring 的 IoC 容器深度绑定了。使用EnableConfigurationProperties可以让实体类只保留ConfigurationProperties纯粹声明属性前缀由配置类去决定何时将它纳入 Spring 容器。2. 总结与最佳实践其实随着 Spring Boot 的演进现在处理配置绑定有三种流派可以根据你的实际场景来选择方式写法特征适用场景方式 1包扫描你的写法ComponentConfigurationProperties普通业务模块。自己项目里随便写简单粗暴直接注入。方式 2显式启用官方经典ConfigurationProperties 配置类上的EnableConfigurationProperties开发通用组件 / Starter。需要规避包扫描限制或者需要与其他Conditional条件联合按需加载。方式 3配置树扫描Spring Boot 2.2 新特性POJO 上只写ConfigurationProperties然后在启动类加上ConfigurationPropertiesScan大型业务系统。统一管理所有配置类既不用写Component也不用在每个配置类上写 Enable 注解。3.Profile多环境隔离根据当前激活的 Profile如dev、test、prod选择性加载配置类。Configuration Profile(dev) // 仅在 spring.profiles.activedev 时生效 public class DevDatabaseConfig { Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder().build(); // 使用内存数据库 } }6. Spring Boot 自动配置原理扩展当你在编写可复用的Starter 模块时配置类的注册机制有所不同Spring Boot 2.7 前在META-INF/spring.factories中配置配置类。Spring Boot 3.x 及以上在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中按行写入配置类的全路径名。使用AutoConfigurationSpring Boot 2.7 引入替代标准的Configuration作为自动配置类的入口配合AutoConfigureBefore/AutoConfigureAfter控制加载顺序。7. 最佳实践清单单功能原则按业务模块拆分配置类如RedisConfig、SecurityConfig、SwaggerConfig避免产生“超级配置类”。善用proxyBeanMethods false若Bean方法之间不需要互相调用来保证单例建议设置为false提升系统启动速度。优先使用参数注入在Bean方法定义中直接将依赖写入方法参数利用 Spring 自动装配减少定义不必要的内部字段。合理使用ConditionalOnMissingBean在封装公共 SDK/组件时始终用该注解留出拓展口方便业务侧自定义覆盖。二、Component Bean的组合在Component类中使用Bean和在Configuration类中使用Bean底层有着非常核心且致命的区别。还记得我们在第一讲提到的“Full 模式”和“Lite 模式”吗这就是它们最大的差异。核心区别是否生成代理对象保证单例ConfigurationBeanFull 模式Spring 会使用 CGLIB 给这个配置类生成一个代理子类。当你在一个Bean方法中调用另一个Bean方法时代理对象会拦截这个调用去 Spring 容器里拿单例对象。ComponentBeanLite 模式Spring不会生成代理类。类就是一个普通的 Java 类。如果你在内部发生方法互调就是纯粹的 Java 方法执行会每次都new出一个全新的对象从而破坏 Spring 默认的单例机制代码对比假设我们有两个 BeanUserDao和UserServiceUserService依赖UserDao。场景 1使用ComponentLite 模式 - 会出问题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 被创建了。Spring 扫描到Bean userDao()调用一次注册到容器。Spring 扫描到Bean userService()执行内部的userDao()方法由于没有代理拦截这就是普通 Java 方法调用又new了一个全新的UserDao。后果UserService里注入的UserDao和 Spring 容器里管理的那个UserDao不是同一个对象场景 2使用ConfigurationFull 模式 - 安全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了。”那么什么时候可以用ComponentBean如果你定义的多个Bean之间完全独立不需要互相调用那么使用ComponentBean是完全没问题的甚至更好因为不需要生成 CGLIB 代理Spring 的启动速度会稍微快一点内存消耗也少一点。这种写法在 Spring 源码内部比如一些自动配置类经常被使用。为了避免掉坑最佳实践建议如果你在Component中定义Bean遇到需要依赖其他 Bean 的情况坚决不要用方法调用而是用参数注入。正确的ComponentBean写法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出来的效果是一样的而且更轻量。