当前位置: 首页 > news >正文

探秘SpringBoot启动流程:原理解析与自定义扩展

引言

SpringBoot是当今Java开发中最受欢迎的微服务框架之一,其简化了Java应用的开发和部署过程。了解SpringBoot的启动流程对于深入理解其原理和内部机制至关重要。本文将深入分析SpringBoot的启动过程,探讨其中的关键步骤和机制,后基于这些机制,我们尝试做一些扩展和一些个性化内容。

启动流程分析

流程图

SpringBoot 启动流程

源代码

// org.springframework.boot.SpringApplication
public class 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 = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}listeners.started(context, timeTakenToStartup);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}catch (Throwable ex) {handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}
}

初始化 SpringApplication 实例

在SpringBoot应用启动时,首先会初始化一个SpringApplication实例。在这个过程中,会进行一系列配置和准备工作,包括:

指定资源加载器和主类

通过指定资源加载器和主类(即ApplicationStarter类),SpringBoot能够正确地加载应用所需的资源,并确定应用的入口点。

指定Web应用类型

SpringBoot支持多种Web应用类型,包括无Web环境、Servlet环境和响应式环境。通过指定不同的应用类型,可以适配不同的部署场景。

获取系统初始化器和监听器

SpringBoot允许用户注册自定义的初始化器和监听器,这些组件可以在应用启动过程中执行特定的逻辑,如配置环境、加载资源等。

执行 SpringApplication#run 方法

一旦SpringApplication实例初始化完成,接下来就会执行run方法来启动SpringBoot应用。在这个过程中,会经历以下关键步骤:

创建启动器上下文

首先,会创建一个启动器上下文,用于管理应用的生命周期和组件。

获取监听器集合

获取所有注册的SpringApplicationRunListener,并通知它们应用即将启动的事件。

处理环境相关事项

在启动过程中,会处理环境相关的配置,包括创建和配置ConfigurableEnvironment,并根据配置信息打印Banner。

创建和初始化应用上下文

这是整个启动过程的核心。首先,会创建一个ConfigurableApplicationContext,然后对其进行初始化。这个过程包括设置环境、注册单例Bean、初始化BeanFactory等。

启动应用上下文

一旦应用上下文初始化完成,就会启动应用上下文的生命周期,执行一系列初始化和准备工作。

通知监听器应用已启动

在应用启动完成后,会通知所有注册的监听器应用已经启动,可以执行相应的逻辑。

执行自定义逻辑

最后,会执行注册的ApplicationRunnerCommandLineRunnerrun方法,这些方法中可以执行一些自定义的初始化逻辑。

应用扩展示例

在启动流程分析中,我们展示了路程图,其中流程图中蓝色部分就是我们能在 SpringBoot 应用启动过程中的所有扩展点(ApplicationContext IoC 容器生命周期中也还有,如 BeanPostProcessor 等,不过我们这里先不关注)。

下面我们就举出一个上面列出的扩展示例,其他如果有兴趣的朋友也可自行尝试,非常简单。

ApplicationContextInitializer

ApplicationContextInitializer 是 Spring Framework 提供的一个接口,它允许我们在 Spring 应用程序上下文(ApplicationContext)创建之前对其进行自定义初始化。这意味着我们可以在 Spring 容器初始化之前执行一些操作,例如设置环境变量、配置属性等。

下面我们展示一下如何使用:

  • 定义一个 ApplicationContextInitializer 实现,如 MarkusApplicationContextInitializer。
public class MarkusApplicationContextInitializer implements ApplicationContextInitializer {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("MarkusApplicationContextInitializer");// 这里我们可以编写自定义的初始化逻辑}
}
  • 在 resources/ 目录下,创建一个 META-INF/spring.factories 文件,指定上我们的自定义实现。(这里就是 Spring 自己定义的 SPI 机制,框架可以自动发现自定义组件)
org.springframework.context.ApplicationContextInitializer=com.markus.spring.boot.extendsion.MarkusApplicationContextInitializer
  • 启动 SpringBoot
@SpringBootApplication
public class SpringApplicationStarter {/*** Spring Boot 启动时的几个扩展点:*  1. org.springframework.boot.SpringApplication#bootstrapRegistryInitializers*  2. org.springframework.boot.SpringApplication#initializers*  3. org.springframework.boot.SpringApplication#listeners*  4. org.springframework.boot.SpringApplication#callRunners(org.springframework.context.ApplicationContext, org.springframework.boot.ApplicationArguments)**/public static void main(String[] args) {SpringApplication.run(SpringApplicationStarter.class, args);}
}

我们看下效果:

image-20240303001455705

启动 Banner 修改

我们经常看到,在 SpringBoot 启动的时候,会打印如下图所示的 Banner:

image-20240303000610861

那么,我们现在分析完了 SpringBoot 的启动过程后,就知道我们可以自定扩展这个文案,怎么扩展呢?

非常简单,在 application.yml 文件里指定一下自定义 banner 的文件位置,SpringBoot 就可以打印我们指定的文案了。

自定义 banner

好了,现在我们在此启动 SpringBoot,图案就是我们自己设置的了:

image-20240303001031309

本文总结

总结一下,本文深入分析了SpringBoot的启动流程,重点关注了SpringApplication的初始化和启动过程。在初始化SpringApplication实例时,会指定资源加载器、主类以及Web应用类型,并获取系统初始化器和监听器。随后,通过run方法启动SpringBoot应用,其中包括创建启动器上下文、获取监听器集合、处理环境相关事项、创建和初始化应用上下文、启动应用上下文、通知监听器应用已启动等关键步骤。文章还展示了如何通过ApplicationContextInitializer进行应用扩展,以及如何自定义Banner。通过本文,大家可以深入了解SpringBoot的启动过程及其扩展方式,为更深入地学习和应用SpringBoot提供了指导和启示。

相关文章:

  • Mongodb基础(node.js版)
  • C2_W2_Assignment_吴恩达_中英_Pytorch
  • 【简略知识】项目开发中,VO,BO,PO,DO,DTO究竟是何方妖怪?
  • 腾讯云幻兽帕鲁服务器如何安全下载WorldOption.sav文件?
  • 抖音视频批量下载软件|视频评论采集工具
  • 开源视频转码器HandBrake
  • Godot自定义控件样式语法解析
  • Java数据类型(八种基本数据类型 + 四种引用类型)、数据类型转换
  • 机器学习:模型评估和模型保存
  • 【软考】设计模式之访问者模式
  • Redis的主从搭建
  • Linux笔记--GCC
  • 全新2.0版本极其抽象的门(Spring Security)
  • Unity RectTransform·屏幕坐标转换
  • 【研发日记】Matlab/Simulink技能解锁(三)——在Stateflow编辑窗口Debug
  • [译]CSS 居中(Center)方法大合集
  • Babel配置的不完全指南
  • download使用浅析
  • ES学习笔记(12)--Symbol
  • exports和module.exports
  • express.js的介绍及使用
  • HashMap ConcurrentHashMap
  • iOS编译提示和导航提示
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • leetcode讲解--894. All Possible Full Binary Trees
  • mysql常用命令汇总
  • zookeeper系列(七)实战分布式命名服务
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 搞机器学习要哪些技能
  • 如何解决微信端直接跳WAP端
  • 如何进阶一名有竞争力的程序员?
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 我的zsh配置, 2019最新方案
  • 学习JavaScript数据结构与算法 — 树
  • 一道面试题引发的“血案”
  • 最近的计划
  • C# - 为值类型重定义相等性
  • 带你开发类似Pokemon Go的AR游戏
  • 关于Android全面屏虚拟导航栏的适配总结
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #android不同版本废弃api,新api。
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • #数学建模# 线性规划问题的Matlab求解
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (2015)JS ES6 必知的十个 特性
  • (分布式缓存)Redis分片集群
  • (排序详解之 堆排序)
  • (新)网络工程师考点串讲与真题详解
  • (一)Java算法:二分查找
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (转)Google的Objective-C编码规范
  • (转)Windows2003安全设置/维护