一、三种配置加载方式对比总览
我们先通过一张表,把三种方式的核心区别讲清楚,方便你快速建立整体认知:
表格
| 加载方式 | 触发时机 | 核心特点 | 适用场景 |
|---|---|---|---|
@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: 63794. 效果验证
- 引入 redis 依赖、配置连接信息、未自定义
redistemplate:redisconfig自动生效,注入默认的redistemplate和stringredistemplate - 未引入 redis 依赖:
@conditionalonclass条件不满足,redisconfig不加载,不影响项目启动 - 自定义了
redistemplate:@conditionalonmissingbean条件不满足,默认配置不生效,用户的自定义 bean 会覆盖默认配置
十一、底层原理:自动配置 +@conditional的完整执行流程
spring boot 启动时,这套机制分为三步执行:
- 扫描收集自动配置类:启动时扫描所有依赖 jar 包中的
autoconfiguration.imports文件,收集所有配置类,放入「候选列表」(此时未加载,仅登记)。 - 逐个条件判断:遍历候选列表,根据配置类 / 方法上的
@conditional系列注解,执行条件匹配,筛选出满足条件的配置类。 - 加载并注册 bean:满足条件的配置类会被加载,
@bean方法执行并注册 bean;不满足条件的配置类直接跳过,不占用资源。
到此这篇关于spring boot 配置加载全解析:从 @componentscan 到自动配置原理的文章就介绍到这了,更多相关springboot自动配置原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论