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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: SpringBoot 源码解析系列

索引


前言

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

正文

启动第一个springboot应用

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


如何启动的

  • 通过代码我们可以看到启动一个springboot应用,非常简单。 只需要一行代码

SpringApplication.run(SpringBootDemoApplication.class, args);


  • debug进去发现其实就是首先new了一个SpringApplication实例,再执行该实例的run方法. 如图


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来切换环境


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

执行.run后发生了什么

还是先扫一下源码

public ConfigurableApplicationContext run(String... args) {

StopWatch stopWatch = new StopWatch();

stopWatch.start();

ConfigurableApplicationContext context = null;

Collection<SpringBootExceptionReporter> 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
  • 我们继续跟进进去,发现了另外一个核心的方法multicastEvent, 我们查一下 有四个listener注册了ApplicationStartingEvent这个事件
  • 我们随便找了一个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 这个接口实现的
  • 因为我们的事件类型为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的核心容器部分,会单独开几章来讲,敬请期待~
目录
相关文章
|
17天前
|
监控 安全 开发工具
鸿蒙HarmonyOS应用开发 | HarmonyOS Next-从应用开发到上架全流程解析
HarmonyOS Next是华为推出的最新版本鸿蒙操作系统,强调多设备协同和分布式技术,提供丰富的开发工具和API接口。本文详细解析了从应用开发到上架的全流程,包括环境搭建、应用设计与开发、多设备适配、测试调试、应用上架及推广等环节,并介绍了鸿蒙原生应用开发者激励计划,帮助开发者更好地融入鸿蒙生态。通过DevEco Studio集成开发环境和华为提供的多种支持工具,开发者可以轻松创建并发布高质量的鸿蒙应用,享受技术和市场推广的双重支持。
218 11
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
101 5
|
14天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
24天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
108 13
|
20天前
|
域名解析 弹性计算 安全
阿里云服务器租用、注册域名、备案及域名解析完整流程参考(图文教程)
对于很多初次建站的用户来说,选购云服务器和注册应及备案和域名解析步骤必须了解的,目前轻量云服务器2核2G68元一年,2核4G4M服务器298元一年,域名注册方面,阿里云推出域名1元购买活动,新用户注册com和cn域名2年首年仅需0元,xyz和top等域名首年仅需1元。对于建站的用户来说,购买完云服务器并注册好域名之后,下一步还需要操作备案和域名绑定。本文为大家展示阿里云服务器的购买流程,域名注册、绑定以及备案的完整流程,全文以图文教程形式为大家展示具体细节及注意事项,以供新手用户参考。
|
2月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
58 12
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
52 2
|
3月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
96 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。

热门文章

最新文章

推荐镜像

更多