SpringBoot源码学习(一) 启动流程解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: ## 索引 + [SpringBoot源码学习(一) 启动流程解析](https://www.atatech.org/articles/145056) + [SpringBoot源码学习(二) 初始化环境,创建容器,初始化Failure Analyzers](https://www.atatech.org/articles/145181) + [SpringBoot源码学习(三) BeanF

索引

前言

  • 9012年了, java社区里面spring全家桶一直是支柱一般的存在。 其中springboot甚至已经不能以火热来形容了, 甚至已经形成了行业某种意义上的”标准“ 。 所以了解并探究springboot的实现是非常有意义的。
  • 本文基于springboot 2.0.4, spring5 的源代码进行分析。

正文

启动第一个springboot应用

  • 下载测试工程
  • 在IDEA上导入项目 并执行SpringBootDemoApplication.class中的main方法
  • 结果如下

image.png

如何启动的

  • 通过代码我们可以看到启动一个springboot应用,非常简单。 只需要一行代码
SpringApplication.run(SpringBootDemoApplication.class, args);
  • debug进去发现其实就是首先new了一个SpringApplication实例,再执行该实例的run方法. 如图

image.png

new SpringApplication发生了什么

  • 先看一下源码
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = deduceWebApplicationType();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
  • 在new SpringApplication 的时候核心是做了两件事 1. 判断当前spring运行的环境 2. 加载META-INF/spring.factories 并初始化监听器

    • 判断当前spring运行的环境
    • 我们debug到源码里面可以看到, 其实就是根据当前的运行环境有没有某个类来判断的
       private WebApplicationType deduceWebApplicationType() {
           if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                   && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
                   && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
               return WebApplicationType.REACTIVE;
           }
           for (String className : WEB_ENVIRONMENT_CLASSES) {
               if (!ClassUtils.isPresent(className, null)) {
                   return WebApplicationType.NONE;
               }
           }
           return WebApplicationType.SERVLET;
       }
    
      
    • 简单来说,目前springboot支持三种运行环境
       public enum WebApplicationType {
    
       /**
        * The application should not run as a web application and should not start an
        * embedded web server.
            * 简单来说就是没有交互的应用程序
        */
       NONE,
    
       /**
        * The application should run as a servlet-based web application and should start an
        * embedded servlet web server.
            * 普通的WEB应用
        */
       SERVLET,
    
       /**
        * The application should run as a reactive web application and should start an
        * embedded reactive web server.
            * 响应式的WEB应用 还比较新 提供了很多新特性 spring4刚刚支持
        */
       REACTIVE
    
      }
    
    • 可以通过加载不同starter来切换环境

image.png
image.png
image.png

  • 加载META-INF/spring.factories

image.png

  • 实际上setInitializers, setListeners这两个方法就是扫描spring.factories类并把实例加载到内存中

执行.run后发生了什么

还是先扫一下源码

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

其中第一个比较核心是 这两行 获取并启动监听器

    SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
  • 首先springboot会首先获取SpringApplicationRunListeners 根据spring.factories的定义 实际上实例化的时候是EventPublishingRunListener这个listener
  • 看一下EventPublishingRunListener的构造函数
    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
  • 在这里会把所有通过spring.factories实例化的listener添加到SimpleApplicationEventMulticaster中。
  • 第二步执行starting方法, 会发布一个ApplicationStartingEvent

image.png

  • 我们继续跟进进去,发现了另外一个核心的方法multicastEvent, 我们查一下 有四个listener注册了ApplicationStartingEvent这个事件

image.png

  • 我们随便找了一个LoggingApplicationListener 看一下核心代码
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        //在springboot启动的时候
        if (event instanceof ApplicationStartedEvent) {
            onApplicationStartedEvent((ApplicationStartedEvent) event);
        }
        //springboot的Environment环境准备完成的时候
        else if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        //在springboot容器的环境设置完成以后
        else if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent((ApplicationPreparedEvent) event);
        }
        //容器关闭的时候
        else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
                .getApplicationContext().getParent() == null) {
            onContextClosedEvent();
        }
        //容器启动失败的时候
        else if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }
  • springboot如何判断一个listener支持什么事件。 debug进去可以发现是通过GenericApplicationListener 这个接口实现的

image.png

  • image.png
  • 因为我们的事件类型为ApplicationEvent,所以会执行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。

小结

  • 根据上文,我们可以简单的做一下小结,在执行了SpringApplication.run(SpringBootDemoApplication.class, args)后发生了什么
  • new了一个SpringApplication实例

    • 判断当前spring运行的环境
    • 加载META-INF/spring.factories 并初始化监听器
  • SpringApplications实例.run

    • 获取并启动监听器
    • 实例化EventPublishingRunListener
    • 把所有通过spring.factories实例化的listener添加到SimpleApplicationEventMulticaster中。
    • 发布ApplicationStartingEvent 执行注册了这个事件的listener
    • 后面还会进行

      • 构造容器环境
      • 创建容器
      • 实例化Failure Analyzers 处理启动的报错信息
      • 准备容器
      • 刷新容器
      • 刷新容器的后扩展接口
    • 后面因为涉及到了spring的核心容器部分,会单独开几章来讲,敬请期待~
目录
相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
163 0
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
24天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
68 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
61 0
|
1月前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
83 0
|
8天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
21天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
39 3
|
23天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。

推荐镜像

更多
下一篇
无影云桌面