找回密码
立即注册
搜索
发回帖 发新帖

6264

积分

0

好友

794

主题
发表于 3 小时前 | 查看: 5| 回复: 0

虽然 Spring Boot 在日常开发中已经算是 Java 领域的标配了,但仔细回想一下面试经历,与 Spring Boot 直接相关的面试题其实并不算多。本质上,Spring Boot 仍是曾经 SSM 那一套,只是通过各种 starter 简化了配置,很多问题最终还是要回归到 Spring 中去解答。不过这并不意味着 Spring Boot 没有可问的点,其中有一个非常经典的面试题就是:Spring Boot 的启动原理究竟是什么?

在此之前我断断续续聊过相关内容,也带领大家自定义过一个 starter,相信对 starter 的原理已经有了一定基础。所以今天这篇文章会跳过一些过于琐碎的细节,专注于把启动流程系统性地梳理清楚。如果你对更细的环节感兴趣,可以随时翻阅云栈社区上往期的 Spring 系列文章。

一、@SpringBootApplication

要说 Spring Boot 的自动化配置,绕不开项目的启动类注解 @SpringBootApplication,这是整个 Spring Boot 世界的起点。先来看这个注解的定义:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {

}

可以看出,@SpringBootApplication 组合了多个注解的功能:

  • 前四个是元注解,这里不做讨论。
  • 第五个 @SpringBootConfiguration 是一个支持配置类的注解,也不展开。
  • 第六个 @EnableAutoConfiguration 表示开启自动化配置,这是今天要聊的重点。
  • 第七个 @ComponentScan 是包扫描注解。为什么 Spring Boot 项目中的 Bean 只要放对位置就会被自动扫描到,就和它有关。

别看这里注解多,真正由 Spring Boot 提供的其实只有两个:@SpringBootConfiguration 和 @EnableAutoConfiguration。其余注解在 Spring Boot 出现之前就已经存在多年了。

二、@EnableAutoConfiguration

接下来看看 @EnableAutoConfiguration 是如何实现自动化配置的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

这个注解起关键作用的就是两个东西:

  1. @AutoConfigurationPackage:表示自动扫描各种第三方的注解。之前我已经详细分析过这个注解的作用,传送门:@AutoConfigurationPackage 和 @ComponentScan 有何区别?
  2. @Import:导入 AutoConfigurationImportSelector 配置类,这个类内部负责加载各种自动化配置类。

三、AutoConfigurationImportSelector

AutoConfigurationImportSelector 类中的方法比较多,入口是 process 方法,我们就从这里开始看:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
        () -> String.format("Only %s implementations are supported, got %s",
            AutoConfigurationImportSelector.class.getSimpleName(),
            deferredImportSelector.getClass().getName()));
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

从类名就能看出来,真正加载自动化配置对象的是这一行:

AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);

getAutoConfigurationEntry 方法实际上就在当前类中,来看实现:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

这里的方法命名都做到了见名知意,日常开发中很值得学习。下面逐个拆解关键方法。

3.1 isEnabled

首先调用 isEnabled 方法判断自动化配置是否开启。即使项目中引入了 spring-boot-starter-xxx,也可以通过配置 spring.boot.enableautoconfiguration=false 来关闭所有自动化配置。

相关源码如下:

protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(
            EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}

3.2 getCandidateConfigurations

接下来调用 获取所有候选的自动化配置类 方法,这些候选配置类主要来自两个地方:

  1. 在自定义 starter 时,需要在 classpath:META-INF/spring.factories 中定义出所有的自动化配置类,这是来源一。
  2. Spring Boot 自带的自动化配置类,位于 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。

相关源码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList<>(
        SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
        .forEach(configurations::add);
    Assert.notEmpty(configurations,
        "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

加载到的自动化配置类全路径被存入 configurations 对象,获取途径有两个:

  1. 调用 SpringFactoriesLoader.loadFactoryNames 方法,本质上就是去加载 META-INF/spring.factories 文件,里面定义了大量的自动化配置类全路径。
  2. 调用 ImportCandidates.load 方法,加载 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的配置类。

如果两个地方都没有加载到任何自动化配置类,就会抛出异常。

3.3 removeDuplicates

removeDuplicates 方法负责移除候选配置类中重复的项,思路很有意思,就是用一个 LinkedHashSet 中转一下:

protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

源码里确实藏着不少这种简洁有效的解决思路。

3.4 getExclusions

getExclusions 方法表示获取所有被排除的自动化配置类,可以从三个地方获取:

  1. 当前注解的 exclude 属性。
  2. 当前注解的 excludeName 属性。
  3. application.properties 配置文件中的 spring.autoconfigure.exclude 属性。

来看相关源码:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(asList(attributes, "excludeName"));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

正好和上面说的三点一一对应。

3.5 checkExcludedClasses

这个方法用来检查所有被排除的自动化配置类。由于 Spring Boot 中的自动化配置类可以自定义,并不需要统一实现某个接口或继承某个类,所以写排除类时如果写错了,编译阶段是校验不出来的,比如这样:

@SpringBootApplication(exclude = HelloController.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

由于 HelloController 并不是一个自动化配置类,项目启动时就会报错:

Spring Boot 排除非自动配置类导致的 IllegalStateException 异常日志

这个异常就来自 checkExcludedClasses 方法,来看实现:

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    for (String exclusion : exclusions) {
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
                && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}

protected void handleInvalidExcludes(List<String> invalidExcludes) {
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
        message.append("\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String.format(
        "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
        message));
}

在 checkExcludedClasses 方法中,会先找出所有位于当前类路径下、却不包含在 configurations 中的被排除类。由于 configurations 中已经是全部自动化配置类,所以这些不存在的类都是有问题的,并不是自动化配置类。这些有问题的类会被收集到 invalidExcludes 中,再进入额外的处理流程。

所谓额外处理,就是在 handleInvalidExcludes 方法中抛出异常,前面截图里的异常就来自这里。

3.6 removeAll

这个方法只做一件事:从 configurations 中移除掉那些被排除的自动化配置类。configurations 本身是 List,exclusions 是 Set,直接调用 removeAll 即可。

3.7 filter

现在所有自动化配置类都已经加载完成了,但这些配置类并不会全部生效,具体是否生效还要看项目是否引入了对应的依赖。

举个例子,现在加载的配置里包含 RedisAutoConfiguration,它是用来自动配置 Redis 的,但如果项目中没有使用 Redis,这个自动化配置类就不会生效。这个过程正是由 getConfigurationClassFilter().filter(configurations) 完成的。

先说预备知识:

由于项目中的自动化配置类特别多,每个配置类都会依赖别的类,当依赖的类存在时,该配置类才会生效。这一堆互相之间的依赖关系存在于 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 文件之中。随便举一个该文件中的配置:

  • org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit

这表示 RabbitAnnotationDrivenConfiguration 生效的必备条件是当前项目类路径下存在 org.springframework.amqp.rabbit.annotation.EnableRabbit。

再看看 RabbitAnnotationDrivenConfiguration 类的注解:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
}

这个类和配置文件中的内容完全一致。

搞懂这个预备知识,后面的内容就好理解了。

先看 getConfigurationClassFilter 方法,它负责获取所有的过滤器:

private ConfigurationClassFilter getConfigurationClassFilter() {
    if (this.configurationClassFilter == null) {
        List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
        for (AutoConfigurationImportFilter filter : filters) {
            invokeAwareMethods(filter);
        }
        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    }
    return this.configurationClassFilter;
}

这里获取到的过滤器都是 AutoConfigurationImportFilter 类型的,这个类型只有三个实例,如下图所示:

Spring Boot 自动配置导入过滤器类图

从三个实例的名字就能大致看出各自的作用:

  • OnClassCondition:条件注解 @ConditionalOnClass 的判定条件,用来判断当前 classpath 下是否存在某个类。
  • OnWebApplicationCondition:条件注解 ConditionalOnWebApplication 的判定条件,用来判断当前系统环境是否是一个 Web 环境。
  • OnBeanCondition:条件注解 @ConditionalOnBean 的判定条件,用来判断当前系统下是否存在某个 Bean。

获取到的三个 AutoConfigurationImportFilter 过滤器就是上面这三个。接下来执行 filter 方法:

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : this.filters) {
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate);
        }
    }
    return result;
}

这里遍历三个过滤器,分别调用各自的 match 方法和 144 个自动化配置类进行匹配。如果某个自动化配置类所需的条件得到满足,match 数组对应位置就是 true,否则为 false。

然后遍历 match 数组,将不满足条件的自动化配置类置为 null,最后再把这些 null 移除掉。

这样就拿到了真正需要执行自动化配置的类。

最后一句 fireAutoConfigurationImportEvents 是触发自动化配置类导入事件,这里就不展开了。

当这些自动化配置类加载进来之后,接下来就是各种条件注解来决定这些配置类是否生效了,这部分相对简单,之前也讲过多次,不再赘述。

经过上面的梳理,相信你对 Spring Boot 启动原理已经有了一个整体的认知。总结一下核心链路:从 @SpringBootApplication 出发,经由 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector,在 getAutoConfigurationEntry 方法中完成候选配置类的加载、去重、排除、过滤四个关键步骤,最终得到实际生效的自动化配置类列表。搞懂了这条主线,再去深挖某个具体 starter 的实现就会顺畅很多。




上一篇:汽车安全攻击面建模:从物理接触到远程的5步红队地图
下一篇:功率半导体产业链拆解:SiC/GaN、设备与国产替代全景
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-7 23:05 , Processed in 0.092170 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表