java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot自动配置原理

Spring Boot 配置加载全解析从 @ComponentScan 到自动配置原理解析

作者:Han.miracle

本文给大家介绍了Spring Boot 配置加载全解析从@ComponentScan到自动配置原理,通过实战案例展示了如何基于@Conditional实现JDK版本条件装配,并了详细步骤和配置原理,感兴趣的朋友一起看看吧

一、三种配置加载方式对比总览

我们先通过一张表,把三种方式的核心区别讲清楚,方便你快速建立整体认知:

表格

加载方式触发时机核心特点适用场景
@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. 核心特点与优缺点

优点

缺点

4. 适用场景

三、方式二:@Import —— 强制导入的 “硬编码”

1. 什么是 @Import?

@Import是 Spring 提供的显式导入注解,它的作用是:

直接将指定的类导入到 Spring 容器中,不管这个类是否被扫描到,也不管它是否被使用。

简单说,就是 “写了就导入,不写就不导入”,是一种强制的、无条件的加载方式。

2. 基本使用示例

场景 1:导入单个配置类

比如你的RedisConfigcommon模块,无法被业务模块的@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. 核心特点与优缺点

优点

缺点

4. 适用场景

四、方式三: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

满足条件的配置类,会被 Spring 容器加载,里面的@Bean方法会被执行,生成对应的 Bean 注册到容器中;不满足条件的配置类,会被直接跳过,不会被加载。

这就是你说的 “只有使用的时候才加载” 的底层原理!

4. 核心特点与优缺点

优点

缺点

5. 适用场景

五、三种方式的核心区别深度对比

我们再回到最开始的问题,把三种方式的核心区别,用最直白的话讲透:

1. 加载时机与条件

2. 适用范围

3. 维护成本

六、总结:怎么选?

看到这里,很多同学可能会问:“我在项目里到底该用哪种方式?”

给你一个直接的选择建议:

  1. 项目内部的业务配置:优先用@ComponentScan,简单直接,无需额外配置
  2. 临时跨包导入少量配置:用@Import,灵活可控,适合临时场景
  3. 多模块复用的公共配置 / Starter:必须用AutoConfiguration.imports,实现按需加载、引入即生效

考虑到其他项目也可能会操作 Redis,我们可以把 RedisConfig 放置在 bite-common 包下。

思考与问题

  1. 问题一:放在 bite-common 包下的配置类,不在业务项目的 @ComponentScan 扫描路径下,@Configuration 注解无法生效。解决办法:借助 Spring Boot 的自动配置机制,让 RedisConfig 被 Spring 管理。
  2. 问题二:如果直接用自动配置管理 RedisConfig,所有引用 bite-common 的工程,都会强制加载这个 Bean,不符合 “按需加载” 的需求。解决办法:借助 @Conditional 系列注解,实现根据条件动态装配 Bean

七、核心基础:@Conditional注解原理

@Conditional 是 Spring 4.0 引入的注解,允许开发者根据特定条件,控制 Bean 的注册与装配,通常与 @Configuration@Bean 配合使用。

Spring 在解析配置类时,会根据 @Conditional 配置的条件,决定是否要装配对应的 Bean:

1.@Conditional注解定义

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

2.Condition接口:条件匹配规则

Condition 是一个函数式接口,matches 方法就是自定义条件逻辑的入口:

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

八、实战案例:基于@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. 效果验证

十一、底层原理:自动配置 +@Conditional的完整执行流程

Spring Boot 启动时,这套机制分为三步执行:

  1. 扫描收集自动配置类:启动时扫描所有依赖 jar 包中的 AutoConfiguration.imports 文件,收集所有配置类,放入「候选列表」(此时未加载,仅登记)。
  2. 逐个条件判断:遍历候选列表,根据配置类 / 方法上的 @Conditional 系列注解,执行条件匹配,筛选出满足条件的配置类。
  3. 加载并注册 Bean:满足条件的配置类会被加载,@Bean 方法执行并注册 Bean;不满足条件的配置类直接跳过,不占用资源。

到此这篇关于Spring Boot 配置加载全解析:从 @ComponentScan 到自动配置原理的文章就介绍到这了,更多相关SpringBoot自动配置原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文