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

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

什么是 SpringBoot ?


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


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


SpringBoot 有哪些新特性?


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


更快速的构建能力


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


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


<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 中,只需要以下一个依赖就够了:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


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


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



起步即可依赖


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


640.png


内嵌容器支持


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


o.s.b.w.embedded.tomcat.TomcatWebServer :Tomcat started on port(s): 8080 (http) with context path


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


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


<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>


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


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 个接口,接口请求地址和代表含义如下表所示:


640.png

SpringBoot 的启动流程


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


SpringApplication.run (Application.class, args)


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


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 图片,默认的如下所示:


640.png


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


.::::.
                  .::::::::.
                 :::::::::::
              ..:::::::::::'
           '::::::::::::'
             .::::::::::
        '::::::::::::::..
             ..::::::::::::.
           ``::::::::::::::::
            ::::``:::::::::'        .:::.
           ::::'   ':::::'       .::::::::.
         .::::'      ::::     .:::::::'::::.
        .:::'       :::::  .:::::::::' ':::::.
       .::'        :::::.:::::::::'      ':::::.
      .::'         ::::::::::::::'         ``::::.
  ...:::           ::::::::::::'              ``::.
 ```` ':.          ':::::::::'                  ::::..
                    '.:::::'                    ':'````..


8. 创建应用上下文


创建 ApplicationContext 上下文对象。


9. 实例化异常报告器


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


10. 准备应用上下文


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


11. 刷新应用上下文


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


12. 事件处理


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


13. 停止计时器监控类


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


14. 输出日志信息


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


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


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


16. 执行所有 Runner 运行器


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


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


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


18. 返回应用上下文对象


至此,SpringBoot 启动完成。


巨人的肩膀



总结


这篇聊了聊 Spring 和 SpringBoot 的区别、SpringBoot 的四个特性、最后还从源码角度介绍了 SpringBoot 的启动顺序。

相关文章
|
NoSQL 搜索推荐 Java
SpringBoot整合Redis启动时卡死问题记录
SpringBoot整合Redis启动时卡死问题记录
1283 0
|
Java
Springboot集成SpringCloud启动后Eureka报错
Springboot集成SpringCloud启动后Eureka报错
323 0
Springboot集成SpringCloud启动后Eureka报错
|
Java API 调度
[Java Framework] SpringBoot几种启动后自动初始化的几种方式
业务需求需要在项目启动之后自动把执行一次方法 (数据初始化或者创建一些调度任务),但是有时候可能不太明确他们的执行顺序,本文就带你梳理一下它们的执行顺序
357 0
[Java Framework] SpringBoot几种启动后自动初始化的几种方式
|
前端开发 Java 应用服务中间件
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
398 0
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
|
Java 应用服务中间件 容器
springboot如何启动内置tomcat?(源码详解)
springboot如何启动内置tomcat?(源码详解)
springboot如何启动内置tomcat?(源码详解)
|
Java Spring
自定义SpringBoot项目的启动Banner
``Banner``是``SpringBoot``框架一个特色的部分,其设计的目的无非就是一个框架的标识,其中包含了版本号、框架名称等内容,既然``SpringBoot``为我们提供了这个模块,它肯定也是可以更换的这也是``Spring``开源框架的设计理念。
|
NoSQL Java Maven
springboot测试Redis连接,启动之后各种报错的解决方案
springboot测试Redis连接,启动之后各种报错的解决方案
springboot测试Redis连接,启动之后各种报错的解决方案
|
Java Linux Maven
《SpringBoot系列六》:SpringBoot应用jar包启动原理
SpringBoot Jar包启动原理? SpringBoot Jar包目录结构? JarLauncher的运行原理?
1208 0
《SpringBoot系列六》:SpringBoot应用jar包启动原理
|
Java
启动SpringBoot项目,报错:无法配置数据源:未指定“url”属性,并且无法配置嵌入式数据源。求解求解
启动SpringBoot项目,报错:无法配置数据源:未指定“url”属性,并且无法配置嵌入式数据源。求解求解
447 0
启动SpringBoot项目,报错:无法配置数据源:未指定“url”属性,并且无法配置嵌入式数据源。求解求解