首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >爆肝3万字,SpringBoot原理深入以及源码分析

爆肝3万字,SpringBoot原理深入以及源码分析

作者头像
老周聊架构
发布2025-11-20 10:34:40
发布2025-11-20 10:34:40
40
举报

我们都知道,Spring有它强大的地方,也有它繁琐的地方,毕竟如日中天的Spring全家桶太强大了,所以导致依赖各种JAR包维护起来费劲,还有编写各种XML配置文件。这两个痛点SpringBoot可以优雅的实现解决。背后当然是SpringBoot约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式SpringBoot 是所有基于Spring开发的项目的起点。SpringBoot的设计就是为了让你尽可能快的跑起来Spring应用程序并且尽可能减少你的配置文件。

了解了SpringBoot出现的背景及优势后,我们就开始从下面几个方面来分析SpringBoot的原理以及源码分析。

一、依赖管理

问题1:为什么导入dependency时不需要指定版本?

在SpringBoot入门程序中,项目pom.xml文件中有两个核心依赖:

  • spring-boot-starter-parent
  • spring-boot-starter-web

1、spring-boot-starter-parent

代码语言:javascript
复制
<!--SpringBoot 父项目依赖管理-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

我们再使用“ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,如下:

代码语言:javascript
复制
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.3.1.RELEASE</version>
</parent>

继续查看spring-boot-dependencies的源文件:

代码语言:javascript
复制
<properties>
  <activemq.version>5.15.12</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.80</appengine-sdk.version>
  <artemis.version>2.12.0</artemis.version>
  <aspectj.version>1.9.5</aspectj.version>
  <assertj.version>3.16.1</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.3</awaitility.version>
  <bitronix.version>2.1.4</bitronix.version>
  // ...
</properties>

spring-boot-dependencies的源文件可以看出,该文件通过标签对一些常用的技术框架的依赖文件进行了统一的版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.3.1版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。

需要注意的是,如果pom.xml引入的依赖文件不是spring-boot-starter-parent管理的,那么在pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号。

问题2:spring-boot-starter-parent父依赖启动器的主要作用是进行统一的版本管理,那么项目运行依赖的JAR包从何而来?

2 spring-boot-starter-web

查看spring-boot-starter-web的源文件:

代码语言:javascript
复制
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.3.1.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.1.RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.1.RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.3.1.RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <scope>compile</scope>
</dependency>
</dependencies>

Spring Boot 除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖。我们可以打开Spring Boot的官方文档,搜索starters关键字查询场景依赖启动器。

Spring Boot并不是提供了所有场景的开发的技术框架都提供了场景启动器,例如MyBatis、Druid等。为了利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进行整合,实现了各自的依赖启动器。

二、自动配置(启动流程)

概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目。

问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication能够扫描Spring组件并自动配置Spring Boot

下面,查看@SpringBootApplication内部源码进行分析,核心代码具体图如下:

代码语言:javascript
复制
@SpringBootApplication
public class SpringBootExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootExampleApplication.class, args);
    }

}
代码语言:javascript
复制
@Target(ElementType.TYPE)    // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented// 表示注解可以记录在javadoc中
@Inherited   // 表示可以被子类继承该注解

@SpringBootConfiguration// 标明该类为配置类
@EnableAutoConfiguration// 启动自动配置功能
@ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

 ...

}

从上面源码可以看出,@SpringBootApplication是一个组合注解,前面4个是注解的元数据信息,我们主要看后面3个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解说明具体如下:

1、@SpringBootConfiguration

@SpringBootConfiguration注解表示Spring Boot配置类。查看@SpringBootConfiguration注解源码如下:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration //配置类
public @interface SpringBootConfiguration {

}

可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是由Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。

2、@EnableAutoConfiguration

@EnableAutoConfiguration注解表示开启自动配置功能,该注解是Spring Boot框架最重要的注解,也是实现自动化配置的注解。我们同样来查看该注解的源代码:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage// 自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class)  // 可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {

 String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

 Class<?>[] exclude() default {};

 String[] excludeName() default {};

}

下面,对这两个核心注解分别讲解:

2.1 @AutoConfigurationPackage

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

// spring框架的底层注解,它的作用就是给容器中导入某个组件类,
// 例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
@Import(AutoConfigurationPackages.Registrar.class)  // 默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
public @interface AutoConfigurationPackage {

}

@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现。

代码语言:javascript
复制
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

// 获取的是项目主程序启动类所在的目录
// metadata:注解标注的元数据信息
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
  register(registry, new PackageImport(metadata).getPackageName());
 }

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
 }

}

使用debug跟踪可以发现new PackageImport(metadata).getPackageName()就是com.riemann

也就是说@AutoConfigurationPackage注解的主要的作用就是将主程序类所在包及所有子包下的组件扫描到Spring容器中。

因此,在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类运行业务开发,这样才能保证定义的类能够被组件扫描器扫描。

2.2 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector这个类导入到Spring容器中,AutoConfigurationImportSelector可以帮助Spring Boot应用将所有符合条件的@Configuration配置都加载到当前Spring Boot创建并使用的IoC容器(ApplicationContext)中,通过源码分析这个类是通过selectImports这个方法告诉Spring Boot都需要导入哪些组件:

代码语言:javascript
复制
// 这个方法告诉springboot都需要导入那些组件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
 }
// 1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
// 作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
// 自动配置的类全名.条件=值
 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

深入研究loadMetadata方法:

代码语言:javascript
复制
final class AutoConfigurationMetadataLoader {

protectedstaticfinal String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; // 文件中为需要加载的配置类的类路径

private AutoConfigurationMetadataLoader() {
 }

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
// 重载方法
return loadMetadata(classLoader, PATH);
 }

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
   // 1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
            // 获得 PATH 对应的 URL 们
   Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            // 遍历 URL 数组,读取到 properties 中
            Properties properties = new Properties();

   // 2.解析urls枚举对象中的信息封装成properties对象并加载
   while (urls.hasMoreElements()) {
    properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
   }
   // 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象

   // 根据封装好的properties对象生成AutoConfigurationMetadata对象返回
   return loadMetadata(properties);
  } catch (IOException ex) {
   thrownew IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
  }
 }
 ...
}

深入研究getAutoConfigurationEntry方法

代码语言:javascript
复制
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 1. 判断是否开启注解。如未开启,返回空串
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
 }
// 2. 获得注解的属性
 AnnotationAttributes attributes = getAttributes(annotationMetadata);

// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
 configurations = removeDuplicates(configurations);
// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
 checkExcludedClasses(configurations, exclusions);
// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
 configurations.removeAll(exclusions);

// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
//@ConditionalOnMissingClass : classpath中不存在该类时起效
//@ConditionalOnBean : DI容器中存在该类型Bean时起效
//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
//@ConditionalOnExpression : SpEL表达式结果为true时
//@ConditionalOnProperty : 参数设置或者值一致时起效
//@ConditionalOnResource : 指定的文件存在时起效
//@ConditionalOnJndi : 指定的JNDI存在时起效
//@ConditionalOnJava : 指定的Java版本存在时起效
//@ConditionalOnWebApplication : Web应用环境下起效
//@ConditionalOnNotWebApplication : 非Web应用环境下起效

// 总结一下判断是否要加载某个类的两种方式:
// 根据spring-autoconfigure-metadata.properties进行判断。
// 要判断@Conditional是否满足
// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
 configurations = filter(configurations, autoConfigurationMetadata);


// 6. 将自动配置导入事件通知监听器
// 当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
// 并触发fireAutoConfigurationImportEvents事件。
 fireAutoConfigurationImportEvents(configurations, exclusions);
// 7. 创建 AutoConfigurationEntry 对象
returnnew AutoConfigurationEntry(configurations, exclusions);
}

getAutoConfigurationEntry里有一个重要的方法getCandidateConfigurations,这个方法是让SpringFactoriesLoader去加载一些组件的名字。

代码语言:javascript
复制
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 让SpringFactoryLoader去加载一些组件的名字
 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 断言,非空
 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

protected ClassLoader getBeanClassLoader() {
returnthis.beanClassLoader;
}

该方法主要市区加载一个外部文件,该文件在如下图:

@EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射(Java Reflection)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IoC容器中。

直到看到了spring.factories配置文件对应的那些类才明白了Spring Boot为啥不需要配置各种XML文件了。那是因为这些配置类的本质是传统Spring MVC框架中对应的XML配置文件,只不过在Spring Boot中以自动配置类的形式进行了预先配置。

因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改。

2.3 小结

Spring Boot底层实现自动配置的步骤是:

  1. Spring Boot 应用启动;
  2. @SpringBootApplication起作用;
  3. @SpringBootConfiguration,标明该类为配置类;
  4. @EnableAutoConfiguration,启动自动配置功能;该注解有两个子注解(@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class));
  5. @AutoConfigurationPackage这个注解主要是@Import(AutoConfigurationPackages.Registrar.class) ,它就是将Registrar这个组件类导入到容器中,而Registrar类作用是扫描主配置类同级目录及其子包里面的所有组件扫描到Spring容器中;
  6. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入容器中,该类的作用是通过selectImports方法执行过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程。

3、@ComponentScan

@ComponentScan注解具体扫描的包的根路径由SpringBoot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项目主程序启动类所在包的具体位置。

总结:

@SpringBootApplication的注解功能就分析的差不多了,简单来说就是三个注解的组合注解:

代码语言:javascript
复制
|- @SpringBootConfiguration
 |- @Configuration // 通过javaConfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
 |- @AutoConfigurationPackage // 自动配置包,与@ComponentScan扫描到的添加到IOC 
 |- @Import(AutoConfigurationImportSelector.class) // 到META-INF/spring.factories中定义的bean添加到IOC容器中 
|- @ComponentScan // 包扫描

三、执行原理

每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。

问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

下面来看下run()方法的源码:

代码语言:javascript
复制
@SpringBootApplication
public class SpringBootExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootExampleApplication.class, args);
    }

}
代码语言:javascript
复制
// 调用静态类,参数对应的就是SpringBootExampleApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // SpringApplication的启动由两部分组成:
// 1. 实例化SpringApplication对象
// 2. run(args):调用run方法
returnnew SpringApplication(primarySources).run(args);
}

1、从上述源码可以看出,SpringApplication.run()内部执行了两个操作,分别是SpringApplication实例的初始化和调用run()启动项目,这两个阶段的源码分析具体如下:

代码语言:javascript
复制
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
 Assert.notNull(primarySources, "PrimarySources must not be null");

// 项目启动类 SpringBootExampleApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

// 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();

// 设置初始化器(Initializer),最后会调用这些初始化器
// 所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

// 设置监听器(Listener)
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
}

从上述源码可以看出,SpringApplication的初始化过程主要包括以下四部分:

1.1、this.webApplicationType = WebApplicationType.deduceFromClasspath();

代码语言:javascript
复制
public enum WebApplicationType {

 NONE,

 SERVLET,

 REACTIVE;

privatestaticfinal String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
   "org.springframework.web.context.ConfigurableWebApplicationContext" };

privatestaticfinal String WEBMVC_INDICATOR_CLASS = "org.springframework."
   + "web.servlet.DispatcherServlet";

privatestaticfinal String WEBFLUX_INDICATOR_CLASS = "org."
   + "springframework.web.reactive.DispatcherHandler";

privatestaticfinal String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

privatestaticfinal String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

privatestaticfinal String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

    /**
     * @return 从 classpath 上,判断 Web 应用类型。
     */
static WebApplicationType deduceFromClasspath() {
        // WebApplicationType.REACTIVE 类型  通过类加载器判断REACTIVE相关的Class是否存在
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) // 判断REACTIVE相关的Class是否存在
    && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
   return WebApplicationType.REACTIVE;
  }
// WebApplicationType.NONE 类型
for (String className : SERVLET_INDICATOR_CLASSES) {
   if (!ClassUtils.isPresent(className, null)) { // 不存在 Servlet 的类
    return WebApplicationType.NONE;
   }
  }
// WebApplicationType.SERVLET 类型。可以这样的判断的原因是,引入 Spring MVC 时,是内嵌的 Web 应用,会引入 Servlet 类。
return WebApplicationType.SERVLET;
 }

static WebApplicationType deduceFromApplicationContext(
   Class<?> applicationContextClass) {
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
   return WebApplicationType.SERVLET;
  }
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
   return WebApplicationType.REACTIVE;
  }
return WebApplicationType.NONE;
 }

private static boolean isAssignable(String target, Class<?> type) {
try {
   return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
  }
catch (Throwable ex) {
   returnfalse;
  }
 }

}

1.2、setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

代码语言:javascript
复制
/**
 * 获得指定类类对应的对象们。
 *
 * @param type 指定类
 * @param <T> 泛型
 * @return 对象们
 * 这里的入参type是:org.springframework.context.ApplicationContextInitializer.class
 *       ApplicationListener
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
  Class<?>[] parameterTypes, Object... args) {
 ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
       // 加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
// org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
// 根据names来进行实例化
 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对实例进行排序
 AnnotationAwareOrderComparator.sort(instances);
return instances;
}

/**
 * 创建对象的数组
 *
 * @param type 父类
 * @param parameterTypes 构造方法的参数类型
 * @param classLoader 类加载器
 * @param args 参数
 * @param names 类名的数组
 * @param <T> 泛型
 * @return 对象的数组
 */
@SuppressWarnings("unchecked")
// parameterTypes: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
  Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
  Set<String> names) {
 List<T> instances = new ArrayList<>(names.size()); // 数组大小,细节~
// 遍历 names 数组
for (String name : names) {
try {
      // 获得 name 对应的类
   Class<?> instanceClass = ClassUtils.forName(name, classLoader);
   // 确认被加载类是ApplicationContextInitializer的子类
   Assert.isAssignable(type, instanceClass);
   // 获得构造方法
   Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
   // 创建对象
   T instance = (T) BeanUtils.instantiateClass(constructor, args);
   // 加入实例列表中
   instances.add(instance);
  } catch (Throwable ex) {
   thrownew IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
  }
 }
return instances;
}
代码语言:javascript
复制
/**
 * ApplicationContextInitializer 数组
 */
private List<ApplicationContextInitializer<?>> initializers;

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
 this.initializers = new ArrayList<>();
 this.initializers.addAll(initializers);
}

1.3、setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

getSpringFactoriesInstances(ApplicationListener.class)与第2步类似。

代码语言:javascript
复制
/**
 * ApplicationListener 数组
 */
private List<ApplicationListener<?>> listeners;
 
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
 this.listeners = new ArrayList<>();
 this.listeners.addAll(listeners);
}

1.4、this.mainApplicationClass = deduceMainApplicationClass();

代码语言:javascript
复制
private Class<?> deduceMainApplicationClass() {
try {
     // 获得当前 StackTraceElement 数组
  StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 判断哪个执行了 main 方法
for (StackTraceElement stackTraceElement : stackTrace) {
   if ("main".equals(stackTraceElement.getMethodName())) {
    return Class.forName(stackTraceElement.getClassName());
   }
  }
 } catch (ClassNotFoundException ex) {
// Swallow and continue
 }
returnnull;
}

2、项目的初始化启动

分析完(new SpringApplication(primarySources)).run(args)源码的前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:

代码语言:javascript
复制
public ConfigurableApplicationContext run(String... args) {
    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
// 初始化应用上下文和异常报告集合
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 属性
 configureHeadlessProperty();


//(1)获取并启动监听器
 SpringApplicationRunListeners listeners = getRunListeners(args);
 listeners.starting();
try {
     // 创建  ApplicationArguments 对象 初始化默认应用参数类
// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

//(2)项目运行环境Environment的预配置
// 创建并配置当前SpringBoot应用将要使用的Environment
// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

  configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
  Banner printedBanner = printBanner(environment);

//(3)创建Spring容器
  context = createApplicationContext();
// 获得异常报告器 SpringBootExceptionReporter 数组
//这一步的逻辑和实例化初始化器和监听器的一样,
// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
  exceptionReporters = getSpringFactoriesInstances(
    SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);


//(4)Spring容器前置处理
//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  prepareContext(context, environment, listeners, applicationArguments,
    printedBanner);

//(5):刷新容器
  refreshContext(context);

//(6):Spring容器后置处理
// 扩展接口,设计模式中的模板方法,默认为空实现。
// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
  afterRefresh(context, applicationArguments);
// 停止 StopWatch 统计时长
  stopWatch.stop();
// 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
//(7)发出结束执行的事件通知
  listeners.started(context);

//(8):执行Runners
// 用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
// Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
// Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
  callRunners(context, applicationArguments);
 } catch (Throwable ex) {
     // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
  handleRunFailure(context, ex, exceptionReporters, listeners);
thrownew IllegalStateException(ex);
 }

       // (9)发布应用上下文就绪事件
// 表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
// 这样整个Spring Boot项目就正式启动完成了。
try {
  listeners.running(context);
 } catch (Throwable ex) {
           // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
           handleRunFailure(context, ex, exceptionReporters, null);
thrownew IllegalStateException(ex);
 }
// 返回容器
return context;
}

2.1 获取并启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args);

代码语言:javascript
复制
private SpringApplicationRunListeners getRunListeners(String[] args) {
 Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
 // 这里仍然利用了getSpringFactoriesInstances方法来获取实例,
 // 从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values
 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
   SpringApplicationRunListener.class, types, this, args));
}

2.2 项目运行环境Environment的预配置 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

代码语言:javascript
复制
/**
 * 加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、
 * servletContextInitParams、systemProperties、sytemEnvironment、random、
 * application.yml(.yaml/.xml/.properties)等;初始化日志系统。
 */
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {

// 获取或创建环境(存在就直接返回,不存在创建一个再返回)
 ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境:配置PropertySources和active Profiles
 configureEnvironment(environment, applicationArguments.getSourceArgs());
// listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
 listeners.environmentPrepared(environment);
// 将环境绑定到SpringApplication
 bindToSpringApplication(environment);
// 如果非web环境,将环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
  environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
 }
// 配置PropertySources对它自己的递归依赖
// 如果有 attach 到 environment 上的 MutablePropertySources,则添加到 environment 的 PropertySource 中。
 ConfigurationPropertySources.attach(environment);
return environment;
}

2.3 创建Spring容器 context = createApplicationContext();

代码语言:javascript
复制
protected ConfigurableApplicationContext createApplicationContext() {
    // 根据 webApplicationType 类型,获得 ApplicationContext 类型
// 这里创建容器的类型 还是根据webApplicationType进行判断的,
// 该类型为SERVLET类型,所以会通过反射装载对应的字节码,
// 也就是AnnotationConfigServletWebServerApplicationContext

// 先判断有没有指定的实现类
 Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {

   switch (this.webApplicationType) {
   case SERVLET:
    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
    break;
   case REACTIVE:
    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    break;
   default:
    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
   }
  } catch (ClassNotFoundException ex) {
   thrownew IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex);
  }
 }
// 创建 ApplicationContext 对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

2.4 Spring容器前置处理 prepareContext(context, environment, listeners, applicationArguments,printedBanner);

代码语言:javascript
复制
private void prepareContext(ConfigurableApplicationContext context,
   ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
   ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境,包括各种变量
    context.setEnvironment(environment);

// 设置上下文的 bean 生成器和资源加载器
 postProcessApplicationContext(context);

// 执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
 applyInitializers(context);

// 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
 listeners.contextPrepared(context);

// 记录启动日志
if (this.logStartupInfo) {
  logStartupInfo(context.getParent() == null);
  logStartupProfileInfo(context);
 }
// Add boot specific singleton beans
// 注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
 beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
  beanFactory.registerSingleton("springBootBanner", printedBanner);
 }
if (beanFactory instanceof DefaultListableBeanFactory) {
  ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
 }
// Load the sources
// 加载所有资源
 Set<Object> sources = getAllSources();
 Assert.notEmpty(sources, "Sources must not be empty");
// 加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
 load(context, sources.toArray(new Object[0]));

// 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
       listeners.contextLoaded(context);

// 这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等
}

2.5 刷新容器 refreshContext(context);

代码语言:javascript
复制
private void refreshContext(ConfigurableApplicationContext context) {
    // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
 refresh(context);
// 注册 ShutdownHook 钩子
if (this.registerShutdownHook) {
try {
   // 向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭
   context.registerShutdownHook();
  } catch (AccessControlException ex) {
   // Not allowed in some environments.
  }
 }
}
代码语言:javascript
复制
@Override
public void refresh() throws BeansException, IllegalStateException {
/**
  * 对象锁加锁
  */
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
/**
   * 第一步:刷新前的预处理
   *
   * 表示在真正做refresh操作之前需要准备做的事情:
   *    设置Spring容器启动时间
   *    开启活跃状态,撤销关闭状态
   *    验证环境信息里一些必须存在的属性
   */
  prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
/**
   * 第二步:
   *
   * 获取BeanFactory:默认实现是DefaultListableBeanFactory
   * Bean获取并封装成BeanDefinition对象
   * 加载BeanDefinition 并注册到BeanDefinitionRegistry
   */
  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
/**
   * 第三步:
   *
   * 获取BeanFactory的准备工作:BeanFactory进行一些设置,比如context的类加载器等。
   */
  prepareBeanFactory(beanFactory);

try {
   // Allows post-processing of the bean factory in context subclasses.
   /**
    * 第四步:
    *
    * BeanFactory准备工作完成后进行的后置处理工作
    */
   postProcessBeanFactory(beanFactory);

   // Invoke factory processors registered as beans in the context.
   /**
    * 第五步:
    *
    * 实例化实现了BeanFactoryPostProcessor接口的Bean,并调用接口方法
    */
   invokeBeanFactoryPostProcessors(beanFactory);

   // Register bean processors that intercept bean creation.
   /**
    * 第六步:
    *
    * 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
    */
   registerBeanPostProcessors(beanFactory);

   // Initialize message source for this context.
   /**
    * 第七步:
    *
    * 初始化MessageSource组件(做国际化功能、消息绑定、消息解析)
    */
   initMessageSource();

   // Initialize event multicaster for this context.
   /**
    * 第八步:
    *
    * 初始化事件派发器
    */
   initApplicationEventMulticaster();

   // Initialize other special beans in specific context subclasses.
   /**
    * 第九步:
    *
    * 子类重写这个方法,在容器刷新的时候可以自定义逻辑,如创建Tomcat、Jetty等WEB服务器
    */
   onRefresh();

   // Check for listener beans and register them.
   /**
    * 第十步:
    *
    * 注册应用的监听器,就是注册实现了ApplicationListener接口的监听器bean
    */
   registerListeners();

   // Instantiate all remaining (non-lazy-init) singletons.
   /**
    * 第十一步:
    *
    * 初始化所有剩下的非懒加载的单例bean
    * 初始化创建非懒加载方式的单例Bean实例(未设置属性)
    * 填充属性
    * 初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
    * 调用BeanPostProcessor(Bean的后置处理器)对实例bean进行后置处理
    */
   finishBeanFactoryInitialization(beanFactory);

   // Last step: publish corresponding event.
   /**
    * 第十二步:
    *
    * 完成context的刷新,主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshEvent)
    */
   finishRefresh();
  }

catch (BeansException ex) {
   if (logger.isWarnEnabled()) {
    logger.warn("Exception encountered during context initialization - " +
      "cancelling refresh attempt: " + ex);
   }

   // Destroy already created singletons to avoid dangling resources.
   destroyBeans();

   // Reset 'active' flag.
   cancelRefresh(ex);

   // Propagate exception to caller.
   throw ex;
  }

finally {
   // Reset common introspection caches in Spring's core, since we
   // might not ever need metadata for singleton beans anymore...
   resetCommonCaches();
  }
 }
}

2.6 Spring容器后置处理 afterRefresh(context, applicationArguments);

代码语言:javascript
复制
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
 // 该方法没有实现,可以根据需要做一些定制化的操作。
}

2.7 发出结束执行的事件通知 listeners.started(context);

代码语言:javascript
复制
public void started(ConfigurableApplicationContext context) {
 // 执行所有SpringApplicationRunListener实现的started方法。
 for (SpringApplicationRunListener listener : this.listeners) {
  listener.started(context);
 }
}

2.8 执行Runners callRunners(context, applicationArguments);

代码语言:javascript
复制
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    // 获得所有 Runner 们
 List<Object> runners = new ArrayList<>();
// 获得所有 ApplicationRunner 实现类
 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 获得所有 CommandLineRunner 实现类
 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 排序 runners
 AnnotationAwareOrderComparator.sort(runners);
// 遍历 Runner 数组,执行逻辑
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
   callRunner((ApplicationRunner) runner, args);
  }
if (runner instanceof CommandLineRunner) {
   callRunner((CommandLineRunner) runner, args);
  }
 }
} 

2.9 发布应用上下文就绪事件 listeners.running(context);

代码语言:javascript
复制
public void running(ConfigurableApplicationContext context) {
 // 触发所有 SpringApplicationRunListener 监听器的 running 事件方法。
 for (SpringApplicationRunListener listener : this.listeners) {
  listener.running(context);
 }
}

四、Spring Boot 执行流程图

下面来画一张Spring Boot执行流程图,让大家更清晰的知道Spring Boot整体执行流程与主要启动阶段:

好了,至此,SpringBoot 原理深入以及源码分析要和小伙伴们告一段落了,我们下一篇再见。


欢迎大家关注我的公众号【老周聊架构】,AI、大数据、云原生、物联网等相关领域的技术知识分享。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老周聊架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、依赖管理
  • 二、自动配置(启动流程)
  • 三、执行原理
  • 四、Spring Boot 执行流程图
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档