SpringBoot原理深入及源码剖析

传统的Spring框架实现一个Web服务,需要导入各种依赖AR包,然后编写对应的XML配置文件等,相较而言,Spring Boot显得更加方便、快捷和高效。那么,Spring Boot究竟如何做到这些的呢?
接下来分别针对Spring Boot框架的依赖管理、自动配置和执行流程进行深入分析

依赖管理

问题∶(1)为什么导入dependency时不需要指定版本?在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-starter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下∶

spring-boot-starter-parent依赖

在chapter01项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下∶

<!--Spring Boot父项目依赖管理-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent<11./artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

上述代码中,将spring-bot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的

使用"Ctrl+鼠标左键"进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

继续查看spring-boot-dependencies底层源文件,核心代码具体如下∶

<properties>
    <activemq.version>5.15.11</activemq.version>
    ...
    <solr.version>8.2.0</solr.version>
    <mysql.version>8.0.18</mysql.version>
    <kafka.version>2.3.1</kafka.version>
    <spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
    <spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
    <spring-retry.version>1.2.4.RELEASE</spring-retry.version>
    <spring-security.version>5.2.1.RELEASE</spring-security.version>
    <spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
    <spring-ws.version>3.0.8.RELEASE</spring-ws.version>
    <sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
    <sun-mail.version>${jakarta-mail.version}</sun-mail.version>
    <tomcat.version>9.0.29</tomcat.version>
    <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
    <thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
    ...
 </properties>

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

需要说明的是,如果pom.xml引入的依赖文件不是spring-botstarter-parent管理的,那么在pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号。
(2)问题2∶spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的?

spring-boot-starter-web依赖

查看spring-boot-starter-web依赖文件源码,核心代码具体如下

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
        <exclusions>
            <exclusion>
                <artifactId>tomcat-embed-el</artifactId>
                <groupId>org.apache.tomcat.embed</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖

正是如此,在pom.xml中引入spring-bot-starter-web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring-bot-starter-parent父依赖进行的统一管理。

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


列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要在poxxml文件中导入对应的依赖启动器即可。

需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号

自动配置(启动流程)

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

问题∶Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main(方法,@SpringBootApplication能够扫描Spring组件并自动配置Spring Boot

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

@SpringBootApplication
public class SpringbootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}
@Target({ElementType.TYPE}) // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented // 表示注解可以记录在javadoc中
@Inherited  // 表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan( // 包扫描器
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

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

@springBootConfiguration注解

@SpringBootConfiguration注解表示Spring Boot配置类。查看@SpringBootConfiguration注解源码,核心代码具体如下。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 配置 IOC 容器
public @interface SpringBootConfiguration {
}

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

@EnableAutoConfiguration注解

@EnableAutoConfguration注解表示开启自动配置功能,该注解是Spring Bot框架最重要的注解,也是实现自动化配置的注解。同样,查看该注解内部查看源码信息,核心代码具体如下



可以发现它是一个组合注解,Spring中有很多以Enable开头的注解,其作用就是借助@lmport来收集并注册特定场景相关的bean,并加载到loC容器。@EnableAutoConfiguration就是借助@lmport来收集所有符合自动配置条件的bean定义,并加载到loC容器。
下面,对这两个核心注解分别讲解∶
(1)@AutoConfigurationPackage注解
查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下∶

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导入 Registrar 中注册的组件
public @interface AutoConfigurationPackage {
}

从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@lmport注解实现的,它是spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如@lmportAutoConfigurationPackages.Registrar.cass,它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现∶


从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions0)方法,使用Debug模式启动项目,可以看到选中的部分就是com.lagou。也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。

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

(2)@lmport(AutoConfigurationlmportSelector.class):
将AutoConfigurationlmportSelector这个类导入到spring容器中,AutoConfigurationlmportsSelector可以帮助springboo应用将所有符合条件的@Confguration 配置都加载到当前SpringBoot创建并使用的loC容器(ApplicationCcontext)中

继续研究AutoConfigurationlmportSelector这个类,通过源码分析这个类中是通过selectmports这个方法告诉springboot都需要导入那些组件∶



深入研究loadMetadata方法


深入getCandidateConfigurations方法
方法中有一个重要方法load FactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。


继续点开loadFactory方法

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        //获取出入的键
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
              //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的配置类的全路径信息封装 为Enumeration类对象
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                
                //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

会去读取一个spring.factories的文件,读取不到会表这个错误,我们继续根据会看到,最终路径的长这样,而这个是spring提供的一个工具类

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}

它其实是去加载一个外部的文件,而这文件是在




@EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中

org.springframeworkboot.autoconfigure.EnableutoConfiguration对应的配置项通过反射Java Refletion)实例化为对应的标注了@Configuration的avaConfig形式的配置类,并加载到IOC容器中

以刚刚的项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对Spring MVC运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC校验器等。而这些自动配置类的本质是传统Spring MVC框架中对应的XML配置文件,只不过在Spring Boot中以自动配置类的形式进行了预先配置。因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改

  • 总结
    因此springboot底层实现自动配置的步骤是∶
  1. springboot应用启动;

2.@SpringBootApplication起作用;

3.@EnableAutoConfiguration;

4.@AutoConfigurationPackage∶这个组合注解主要是@lmport(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;

5.@lmport(AutoConfigurationlmportSelector.cdass)∶它通过将AutoConfigurationlmportSelector类导入到容器中,AutoConfigurationlmportSelector类作用是通过selectmports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程

@ComponentScan注解

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

总结∶
@SpringBootApplication的注解的功能就分析差不多了,简单来说就是3个注解的组合注解∶

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

自定义Stater

SpringBoot starter机制
SpringBoo由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter。

starter是SpringBoo诽常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。

例如,你想使用Reids插件,那么可以使用spring·boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb

为什么要自定义starter
开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配

自定义starter的命名规则
SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建议自定义的starter使用xx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter
整个过程分为两部分∶
● 自定义starter
● 使用starter
首先,先完成自定义starter
(1)新建mavenjar工程,工程名为zdy-spring-boot-starter,导入依赖∶

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>2.2.2.RELEASE</version>
    </dependency>
</dependencies>

(2)编写javaBean

package com.study.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {

    private Integer id;

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "SimpleBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

(3)编写配置类MyAutoConfiguration

@Configuration
@ConditionalOnClass // Conditional0OnClass∶当类路径classpath下有指定的类的情况下进行自动配置
public class MyConfiguration {

    static {
        System.out.println("MyAutoConfiguration init....");
    }

    @Bean
    public SimpleBean simpleBean(){
        return new SimpleBean();
    }
}

(4)resources下创建/META-INF/spring.factories
注意∶META-NF是自己手动创建的目录,spring.factories也是手动创建的文件,在该文件中配置自己的自动配置类


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.study.config.MyAutoConfiguration

使用自定义starter
(1)导入自定义starter的依赖

<dependency>
    <groupId>com.study</groupId>
    <artifactId>zdy-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

(2)在全局配置文件中配置属性值

simplebean.id=1
simplebean.name=自定义starter

(3)编写测试方法

// 测试自定义starter
@Autowired
private SimpleBean simpleBean;

@Test
void contextLoads() {
    System.out.println(simpleBean);
}

执行原理

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

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

下面我们查看run()方法内部的源码,核心代码具体如下∶

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化创建和调用run0)启动项目,这两个阶段的实现具体说明如下

SpringApplication实例的初始化创建

查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下

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");

    //项目启动类 SpringbootDemoApplication.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的初始化过程主要包括4部分,具体说明如下。

(1)this.webApplicationType=WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。deduceFromClasspath)方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTVE应用(Spring 5开始出现的WebFlux交互式应用)

(2)thissetnitilizers(this.getSpringFactoriesinstances(ApplicationContextInitializer.cass)
用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器类ApplicationContextInitializer。

(3)this.setListeners(this. getSpringFactoriesinstances(ApplicationListener.dass)
用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factries类路径下的METAINF下的spring factores文件中获取所有可用的监听器类ApplicationListener。

(4)this.mainAplicationClass= this.deduceMainApplicationlass()
用于推断并设置项目main()方法启动的主程序启动类

项目的初始化启动

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

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);
        throw new IllegalStateException(ex);
    }

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

从上述源码可以看出,项目初始化启动过程大致包括以下部分∶
● 第一步∶获取并启动监听器

this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication 实例初始化过程中
初始化的springApplicationRunListener监听器并运行。

●第二步∶根据SpringApplicationRunListeners以及参数来准备环境

this.prepareEnvironment(listeners,applicationArguments)方法主要用于对项目运行环境进行预设置,同
时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境

● 第三步∶创建Spring容器

根据vebApplicationType进行判断,确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字
节码,也就是AnnotationConfigservletwebserverApplicationContext,接着使用之前初始化设置的context(应
用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参
数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置

●第四步∶Spring容器前置处理

这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操
作∶将启动类注入容器,为后续开启自动化配置奠定基础

●第五步∶刷新容器

开启刷新spring容器,通过refresh方法对整个roc容器的初始化(包括bean资源的定位,解析,注册等等),
同时向Jw运行时注册一个关机钩子,在Jv%关机时会关闭这个上下文,除非当时它已经关闭

● 第六步∶Spring容器后置处理

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启
动结束log,或者一些其它后置处理。

● 第七步∶发出结束执行的事件

获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的spring容器传进去了,创建一
个ApplicationstartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里
是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的
starting是直接向springApplication中的监听器发布启动事件。

● 第八步∶执行Runners

用于调用项目中自定义的执行器xxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring 
Boot提供的执行器接口有AplicationRunner和CcommandLinerunner两种,在使用时只需要自定义一个执行器
类实现其中一个接口并重写对应的run()方法接口,然后spring Boot项目启动后会立即执行这些特定程序

下面,通过一个Spring Boot执行流程图,让大家更清晰的知道Spring Boot的整体执行流程和主要启动阶段∶


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341