工作三年,小胖问我 SpringBoot 是怎么启动的?真的离谱!

2021-03-04 14:49:43 浏览数 (1)

哈喽,我是狗哥。这是 Java 面试及源码剖析的第七篇

上篇聊完了 Spring,该聊聊 SpringBoot 了。通过上两篇介绍,相信大家对 Spring 都很熟悉了,它为 Java 程序提供了基础架构的支持,包含很多实用功能。比如:JDBC、AOP、ORM、TEST 等等,有了这些模块的支持,我们缩断开发时间,提高效率,最终升职加薪。

现在的 Java 项目基本都是直接上 SpringBoot,因此在面试中,面试官也会经常问 SpringBoot 相关的问题。比如:SpringBoot 与 Spring 的区别?它的特性?它的启动过程?

什么是 SpringBoot ?

SpringBoot 跟 Spring 是一脉相承的,本质上是 Spring 的延伸和扩展,它就是为了简化 Spring 项目的构建以及开发的过程而生。

举个栗子(我自己的理解,不喜勿喷):如果 Spring 是个汽车引擎;SpringBoot 就是一台汽车,加上油就能开。

SpringBoot 有哪些新特性?

四个,分别是更快速的构建能力、起步即可依赖、内嵌容器支持以及 Actuator 监控

更快速的构建能力

SpringBoot 提供了一堆 Starters 用于快速构建项目,Starters 可以理解为启动器。它包含了一系列可集成到应用中的依赖包,你可以直接在 Pom 引用,而不用到处去找。

比如,在 Spring 中创建一个 Web 程序 Pom 配置的依赖项是这样的:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>xxx</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>xxx</version>
</dependency>

而在 SpringBoot 中,只需要以下一个依赖就够了:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

当我们添加了 starter 模块以后,项目构建的初期就会把 web 所有依赖项自动添加到项目中。这样的例子还有很多,单元测试依赖、数据库依赖、ORM 依赖等等都有相应的 Starter。

常见的 Starter 可以看 SpringBoot 官方文档:

  • https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter

起步即可依赖

SpringBoot 在新建项目时即可勾选依赖项,在项目初始化时就把相关依赖加进去,你需要数据库就把数据库相关 starter 加进去,需要单元测试支持,就把单元测试相关 starter 加进去,这样就大大缩短了去查询依赖的时间。如下图:

SpringBoot 勾选依赖

内嵌容器支持

Spring Boot 内嵌了 Tomcat、Jetty、Undertow 三种容器,也就是说,以往用 Spring 构建 web 项目我们还要配置 Tomcat 等容器,现在不用了。其默认嵌入的容器是 Tomcat 默认端口是 8080,在我们启动 Spring Boot 项目的时候,在控制台上就能看到如下信息:

代码语言:javascript复制
o.s.b.w.embedded.tomcat.TomcatWebServer :Tomcat started on port(s): 8080 (http) with context path

PS:开发中看到以上信息,就意味着 SpringBoot 项目已启动完成。

当然,也可以修改内嵌的容器支持,比如,改成 Jetty :

代码语言:javascript复制
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 <!-- 移处 Tomcat -->
 <exclusions>
  <exclusion>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
  </exclusion>
 </exclusions>
</dependency>
<!-- 换成 jetty 容器 -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

此时再次启动项目,信息如下:

代码语言:javascript复制
o.e.jetty.server.AbstractConnector: Started ServerConnector@53f9009d{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
o.s.b.web.embedded.jetty.JettyWebServer

Actuator 监控

Spring Boot 自带了 Actuator 监控功能,主要用于提供对应用程序监控,以及控制的能力,比如监控应用程序的运行状况,或者内存、线程池、Http 请求统计等,同时还提供了关闭应用程序等功能。

Actuator 提供了 19 个接口,接口请求地址和代表含义如下表所示:

Actuator 接口

SpringBoot 的启动流程

除了问特性之外,面试官往往还会问 SpringBoot 的启动流程。那么它的启动流程是怎样的呢?来探讨下,项目创建完毕之后,会看到主类 Application 中有这样的代码:

代码语言:javascript复制
SpringApplication.run (Application.class, args)

这就是 Spring Boot 程序的入口,那么它的启动流程是怎样的呢?看看源码就知道了:

代码语言:javascript复制
public ConfigurableApplicationContext run(String...args) {

    // 1.创建并启动计时监控类
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 2.声明应用上下文对象和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection < SpringBootExceptionReporter > exceptionReporters = new ArrayList();
    // 3.设置系统属性 headless 的值
    this.configureHeadlessProperty();
    // 4.创建所有 Spring 运行监听器并发布应用启动事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();
    Collection exceptionReporters;
    
    try {
        // 5.处理 args 参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 6.准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        // 7.创建 Banner 的打印类
        Banner printedBanner = this.printBanner(environment);
        // 8.创建应用上下文
        context = this.createApplicationContext();
        // 9.实例化异常报告器
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] {
            ConfigurableApplicationContext.class
        }, context);
        // 10.准备应用上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 11.刷新应用上下文
        this.refreshContext(context);
        // 12.应用上下文刷新之后的事件的处理
        this.afterRefresh(context, applicationArguments);
        // 13.停止计时监控类
        stopWatch.stop();
        // 14.输出日志记录执行主类名、时间信息
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        // 15.发布应用上下文启动完成事件
        listeners.started(context);
        // 16.执行所有 Runner 运行器
        this.callRunners(context
    } catch (Throwable var10
        this.handleRunFailure(context, var10, exceptionReporters, listeners
        throw new IllegalStateException(var10);
    }

    try {
        // 17.发布应用上下文就绪事件
        listeners.running(context);
        // 18.返回应用上下文对象
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners) null);
        throw new IllegalStateException(var9);
    }

}

从以上源码看出,SpringBoot 的启动一共分了 18 个步骤:

1. 创建并启动计时监控类

计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。

2. 声明应用上下文对象和异常报告集合

声明了应用上下文对象和一个异常报告的 ArrayList 集合。

3. 设置系统属性 headless 的值

设置 Java.awt.headless = true,其中 awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true 表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。

4. 创建所有 Spring 运行监听器并发布应用启动事件

获取配置的监听器名称并实例化所有的类。

5. 初始化默认应用的参数类

声明并创建一个应用参数对象。

6. 准备环境

创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。

7. 创建 Banner 的打印类

SpringBoot 启动时会打印 Banner 图片,默认的如下所示:

默认 banner

当然,你可以修改成自己的女朋友,直接在项目 resource 目录下加个 banner.txt 把你想要呈现的内容粘贴进去即可。喏,下面就是拿我女朋友照片制作的。PS,附上 banner 的在线制作链接:https://www.bootschool.net/ascii

代码语言:javascript复制
                    .::::.
                  .::::::::.
                 :::::::::::
              ..:::::::::::'
           '::::::::::::'
             .::::::::::
        '::::::::::::::..
             ..::::::::::::.
           ``::::::::::::::::
            ::::``:::::::::'        .:::.
           ::::'   ':::::'       .::::::::.
         .::::'      ::::     .:::::::'::::.
        .:::'       :::::  .:::::::::' ':::::.
       .::'        :::::.:::::::::'      ':::::.
      .::'         ::::::::::::::'         ``::::.
  ...:::           ::::::::::::'              ``::.
 ```` ':.          ':::::::::'                  ::::..
                    '.:::::'                    ':'````..

8. 创建应用上下文

创建 ApplicationContext 上下文对象。

9. 实例化异常报告器

执行 getSpringFactoriesInstances () 方法获取异常类的名称,并通过反射实例化。

10. 准备应用上下文

把上面步骤已创建好的对象,设置到 prepareContext 中准备上下文。

11. 刷新应用上下文

解析配置文件,加载 bean 对象,并启动内置的 web 容器等等。

12. 事件处理

一些自定义的后置处理操作。

13. 停止计时器监控类

停止此过程第一步中的程序计时器,并统计任务的执行信息。

14. 输出日志信息

把相关的记录信息,如类名、时间等信息进行控制台输出。

15. 发布应用上下文启动完成事件

触发所有 SpringApplicationRunListener 监听器的 started 事件方法。

16. 执行所有 Runner 运行器

执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。

17. 发布应用上下文就绪事件

触发所有的 SpringApplicationRunListener 监听器的 running 事件。

18. 返回应用上下文对象

至此,SpringBoot 启动完成。

巨人的肩膀

  • https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1762

总结

这篇聊了聊 Spring 和 SpringBoot 的区别、SpringBoot 的四个特性、最后还从源码角度介绍了 SpringBoot 的启动顺序。但这还不够,有些面试官可能还会深入问:《SpringBoot 是怎么实现自动配置的?》、《SpringBoot 是如何实现日志的?》恰好我都有写过,感兴趣的小伙伴直接点击即可进入。

0 人点赞