一、三种配置加载方式对比总览
我们先通过一张表,把三种方式的核心区别讲清楚,方便你快速建立整体认知:
表格
| 加载方式 | 触发时机 | 核心特点 | 适用场景 |
|---|---|---|---|
@ComponentScan | 项目启动时,随包扫描一次性加载 | 暴力扫描、全量加载、无条件生效 | 项目内部配置,与启动类在同一包下 |
@Import | 配置类解析时直接导入 | 强制加载、无条件生效、写了就生效 | 临时导入少量跨包配置、第三方类 |
AutoConfiguration.imports(自动配置) | 启动时登记候选,条件判断后按需加载 | 按需加载、条件判断、不用不加载 | 通用 Starter、公共模块、跨项目复用配置 |
接下来,我们就对这三种方式逐一拆解,从使用场景到底层原理,讲得明明白白。
二、方式一:@ComponentScan —— 最直接的 “暴力扫描”
1. 什么是 @ComponentScan?
@ComponentScan是 Spring 最基础的包扫描注解,它会告诉 Spring:
去指定的包路径下,扫描所有被
@Component、@Service、@Repository、@Configuration修饰的类,把它们注册到 Spring 容器中。
在 Spring Boot 中,启动类上的@SpringBootApplication注解,本质上就包含了@ComponentScan,默认扫描启动类所在包及其子包下的所有类。
2. 基本使用示例
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
在这个示例中,@SpringBootApplication会自动扫描com.bite.blog包下的所有组件类,无需额外配置。
如果你的配置类不在启动类的同级或子包下,可以手动指定扫描路径:
@SpringBootApplication(scanBasePackages = "com.bite")
public class BlogApplication {
// ...
}
3. 核心特点与优缺点
✅ 优点:
- 简单直接,无需额外配置,项目内的配置类直接生效
- 无需手动维护导入列表,新增类自动被扫描到
❌ 缺点:
- 暴力扫描,不管类是否被使用,都会被加载到容器中,启动时一次性全量加载
- 跨包、跨模块的配置类无法被扫描到,比如你在
common模块的配置类,在user-service中无法被自动扫描 - 无法实现 “按需加载”,不使用的配置类也会被加载,造成资源浪费
4. 适用场景
- 项目内部的业务配置类,与启动类在同一包结构下
- 单体项目中,所有配置类都在项目内部的场景
三、方式二:@Import —— 强制导入的 “硬编码”
1. 什么是 @Import?
@Import是 Spring 提供的显式导入注解,它的作用是:
直接将指定的类导入到 Spring 容器中,不管这个类是否被扫描到,也不管它是否被使用。
简单说,就是 “写了就导入,不写就不导入”,是一种强制的、无条件的加载方式。
2. 基本使用示例
场景 1:导入单个配置类
比如你的RedisConfig在common模块,无法被业务模块的@ComponentScan扫描到,就可以用@Import手动导入:
@SpringBootApplication
@Import(RedisConfig.class) // 强制导入Redis配置类
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
场景 2:导入多个配置类
@Import({RedisConfig.class, WebConfig.class, SecurityConfig.class})
public class UserServiceApplication {
// ...
}
场景 3:配合 ImportSelector 动态导入
你还可以通过实现ImportSelector接口,动态决定要导入哪些配置类:
public class CustomImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据条件动态返回要导入的类名
return new String[]{RedisConfig.class.getName()};
}
}
// 使用
@Import(CustomImportSelector.class)
public class UserServiceApplication {
// ...
}
3. 核心特点与优缺点
✅ 优点:
- 灵活可控,想导入哪个类就导入哪个类,不受包扫描路径限制
- 可以导入非 Spring 管理的第三方类,无需修改第三方代码
❌ 缺点:
- 无条件强制加载:不管你项目里有没有用到这个类,不管配置是否生效,只要写了
@Import,类就会被加载到容器中 - 硬编码维护,新增配置类需要手动修改启动类,不适合多模块、多项目复用
- 无法实现按需加载,不使用的配置类也会被加载,浪费容器资源
4. 适用场景
- 临时导入少量跨包、跨模块的配置类
- 导入无法被包扫描到的第三方类
- 动态导入配置类的场景(配合
ImportSelector)
四、方式三:AutoConfiguration.imports —— Spring Boot 自动配置的核心
1. 什么是 AutoConfiguration.imports?
从 Spring Boot 2.7 开始,spring.factories被废弃,取而代之的是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,它是 Spring Boot 实现自动配置的核心载体。
简单说,它的作用是:
登记所有自动配置类的全限定名,由 Spring Boot 自动配置机制,根据条件按需加载这些配置类。
你在项目里看到的com.bite.common.config.RedisConfig,就是写在这个文件里的自动配置类。
2. 完整使用示例
步骤 1:创建自动配置类
@Configuration
@ConditionalOnClass(RedisTemplate.class) // 项目中存在RedisTemplate类时才生效
@ConditionalOnProperty(prefix = "spring.redis", name = "host") // 配置了redis.host才生效
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// RedisTemplate配置逻辑
return new RedisTemplate<>();
}
}
步骤 2:创建 AutoConfiguration.imports 文件
在common模块的resources目录下,创建以下路径的文件:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中写入自动配置类的全限定名:
com.bite.common.config.RedisConfig
步骤 3:业务模块引入 common 依赖
<dependency>
<groupId>com.bite</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
此时,业务模块无需任何额外配置,启动时 Spring Boot 会自动加载RedisConfig,并根据条件判断是否生效。
3. 核心原理:自动配置的 “三步流程”
很多同学只知道这个文件能实现自动配置,但不知道它背后的完整流程,我们拆解成三步讲清楚:
第一步:启动扫描,登记候选
Spring Boot 启动时,会自动扫描项目所有依赖的 jar 包中,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,把里面所有的配置类全限定名收集起来,放到一个 “自动配置候选列表” 中。
⚠️ 注意:此时配置类还没有被加载,只是被登记为候选。
第二步:条件判断,筛选生效
Spring Boot 会逐个检查候选列表中的配置类,根据类上的@Conditional系列注解(比如@ConditionalOnClass、@ConditionalOnProperty、@ConditionalOnMissingBean),判断这个配置类是否满足生效条件:
- 项目中是否存在指定的类?
- 配置文件中是否存在对应的配置项?
- 容器中是否已经存在对应的 Bean?
只有满足所有条件的配置类,才会被标记为 “生效”,进入下一步。
第三步:加载生效,注册 Bean
满足条件的配置类,会被 Spring 容器加载,里面的@Bean方法会被执行,生成对应的 Bean 注册到容器中;不满足条件的配置类,会被直接跳过,不会被加载。
这就是你说的 “只有使用的时候才加载” 的底层原理!
4. 核心特点与优缺点
✅ 优点:
- 真正的按需加载,只有满足条件时才会被加载,不使用则不加载,不浪费资源
- 跨模块、跨项目复用,通用配置可以做成 Starter,其他项目引入依赖即可自动生效,无需额外配置
- 解耦性强,配置类可以放在任何包下,不受包扫描路径限制
- 支持条件判断,不同环境、不同依赖下自动适配配置
❌ 缺点:
- 配置路径容易写错,文件路径必须严格按照规范创建,否则无法被扫描到
- 条件注解使用不当,容易出现配置不生效的问题,排查难度比前两种方式稍高
5. 适用场景
- 通用 Starter 开发,比如 Redis、MyBatis、Swagger 等公共配置模块
- 多模块项目中的公共配置,多个业务模块都需要复用的配置
- 第三方组件集成,实现 “引入依赖即生效” 的体验
五、三种方式的核心区别深度对比
我们再回到最开始的问题,把三种方式的核心区别,用最直白的话讲透:
1. 加载时机与条件
@ComponentScan:启动时一次性扫描,无条件全量加载,不管用不用都加载@Import:配置解析时直接导入,无条件强制加载,写了就加载,不管用不用AutoConfiguration.imports:启动时登记候选,条件判断后按需加载,只有满足条件时才加载,不用不加载
2. 适用范围
@ComponentScan:仅适用于项目内部、与启动类在同一包结构下的配置类@Import:适用于临时导入少量跨包、跨模块的配置类AutoConfiguration.imports:适用于跨模块、跨项目复用的通用配置,适合做 Starter
3. 维护成本
@ComponentScan:无需手动维护,新增类自动被扫描,维护成本最低@Import:硬编码维护,新增配置类需要手动修改启动类,维护成本中等AutoConfiguration.imports:配置文件统一维护,新增配置类只需在文件中添加一行,维护成本低,且不影响业务代码
六、总结:怎么选?
看到这里,很多同学可能会问:“我在项目里到底该用哪种方式?”
给你一个直接的选择建议:
- 项目内部的业务配置:优先用
@ComponentScan,简单直接,无需额外配置 - 临时跨包导入少量配置:用
@Import,灵活可控,适合临时场景 - 多模块复用的公共配置 / Starter:必须用
AutoConfiguration.imports,实现按需加载、引入即生效
考虑到其他项目也可能会操作 Redis,我们可以把 RedisConfig 放置在 bite-common 包下。
思考与问题
- 问题一:放在
bite-common包下的配置类,不在业务项目的@ComponentScan扫描路径下,@Configuration注解无法生效。解决办法:借助 Spring Boot 的自动配置机制,让RedisConfig被 Spring 管理。 - 问题二:如果直接用自动配置管理
RedisConfig,所有引用bite-common的工程,都会强制加载这个 Bean,不符合 “按需加载” 的需求。解决办法:借助@Conditional系列注解,实现根据条件动态装配 Bean。
七、核心基础:@Conditional 注解原理
@Conditional 是 Spring 4.0 引入的注解,允许开发者根据特定条件,控制 Bean 的注册与装配,通常与 @Configuration、@Bean 配合使用。
Spring 在解析配置类时,会根据 @Conditional 配置的条件,决定是否要装配对应的 Bean:
- 条件满足:Bean 会被注册到容器中
- 条件不满足:Bean 会被跳过,不会被加载
1. @Conditional 注解定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
value:接收一个Condition接口实现类数组,Spring 会根据这些类的判断结果,决定是否装配 Bean。- 可标注在类上(控制整个配置类),也可标注在
@Bean方法上(控制单个 Bean)。
2. Condition 接口:条件匹配规则
Condition 是一个函数式接口,matches 方法就是自定义条件逻辑的入口:
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches返回true:条件满足,Bean 会被装配matches返回false:条件不满足,Bean 不会被装配
八、实战案例:基于 @Conditional 实现 JDK 版本条件装配
自定义条件类,实现:当前 JDK 版本为 21 时,注册 JDK21 Bean;版本为 17 时,注册 JDK17 Bean。
1. 步骤 1:定义 JDK 版本条件类
// JDK21 生效条件
public class Java21Condition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return JavaVersion.getJavaVersion().equals(JavaVersion.TWENTY_ONE);
}
}
// JDK17 生效条件
public class Java17Condition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return JavaVersion.getJavaVersion().equals(JavaVersion.SEVENTEEN);
}
}
2. 步骤 2:定义配置类与 Bean
@Configuration
public class AppConfig {
@Bean
@Conditional(Java21Condition.class)
public JDK21 jdk21() {
System.out.println("JDK21 初始化完成");
return new JDK21();
}
@Bean
@Conditional(Java17Condition.class)
public JDK17 jdk17() {
System.out.println("JDK17 初始化完成");
return new JDK17();
}
}
// 模拟JDK类
class JDK21 {}
class JDK17 {}
3. 步骤 3:测试效果
@SpringBootTest
class ConditionalTest {
@Test
void test() {
// JDK17环境:只会打印"JDK17 初始化完成"
// JDK21环境:只会打印"JDK21 初始化完成"
}
}
九、Spring Boot 常用 @Conditional 扩展注解
Spring Boot 封装了一套常用的条件注解,覆盖了绝大多数开发场景,无需手动实现 Condition 接口:
| 注解 | 作用 | 使用场景 |
|---|---|---|
@ConditionalOnClass | 类路径中存在指定类时生效 | 判断项目是否引入了 Redis、MyBatis 等依赖 |
@ConditionalOnMissingClass | 类路径中不存在指定类时生效 | 实现 “无依赖时才加载” 的配置 |
@ConditionalOnBean | 容器中存在指定 Bean 时生效 | 依赖其他 Bean 才能加载的配置 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean 时生效 | 实现默认配置,允许用户自定义 Bean 覆盖 |
@ConditionalOnProperty | 配置文件中存在指定属性且值匹配时生效 | 根据配置开关控制配置是否启用 |
@ConditionalOnSingleCandidate | 容器中存在唯一指定类型的 Bean 时生效 | Spring Boot 自动配置中常用(如 RedisConnectionFactory) |
十、实战落地:Redis 自动配置完整实现(结合 AutoConfiguration.imports + @Conditional)
回到 bite-common 模块的 RedisConfig,实现跨模块复用 + 按需加载。
1. 步骤 1:创建 Redis 自动配置类
@Configuration
// 条件1:类路径中存在 RedisTemplate(说明项目引入了Redis依赖)
@ConditionalOnClass(RedisTemplate.class)
// 条件2:配置文件中存在 spring.redis.host(说明项目配置了Redis连接)
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
public class RedisConfig {
@Bean
// 条件3:容器中不存在名为 redisTemplate 的Bean(用户未自定义时才生效)
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 序列化器配置...
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
2. 步骤 2:配置 AutoConfiguration.imports 文件
在 bite-common 模块的 resources 目录下,创建以下文件:src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中写入配置类的全限定名:
com.bite.common.config.RedisConfig
3. 步骤 3:业务模块引入依赖并配置
在需要使用 Redis 的业务模块中,引入 bite-common 依赖:
<dependency>
<groupId>com.bite</groupId>
<artifactId>bite-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
并在 application.yml 中配置 Redis 连接信息:
spring:
redis:
host: 127.0.0.1
port: 6379
4. 效果验证
- 引入 Redis 依赖、配置连接信息、未自定义
RedisTemplate:RedisConfig自动生效,注入默认的RedisTemplate和StringRedisTemplate - 未引入 Redis 依赖:
@ConditionalOnClass条件不满足,RedisConfig不加载,不影响项目启动 - 自定义了
RedisTemplate:@ConditionalOnMissingBean条件不满足,默认配置不生效,用户的自定义 Bean 会覆盖默认配置
十一、底层原理:自动配置 + @Conditional 的完整执行流程
Spring Boot 启动时,这套机制分为三步执行:
- 扫描收集自动配置类:启动时扫描所有依赖 jar 包中的
AutoConfiguration.imports文件,收集所有配置类,放入「候选列表」(此时未加载,仅登记)。 - 逐个条件判断:遍历候选列表,根据配置类 / 方法上的
@Conditional系列注解,执行条件匹配,筛选出满足条件的配置类。 - 加载并注册 Bean:满足条件的配置类会被加载,
@Bean方法执行并注册 Bean;不满足条件的配置类直接跳过,不占用资源。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/HANhylyxy/article/details/160789415



