SpringBoot程序启动流程解析

2024-11-18 11:35
原理篇学习到这里即将结束,最后一章说一下springboot程序的启动流程。对于springboot技术来说,它用于加速spring程序的开发,核心本质还是spring程序的运行,所以于其说是springboot程序的启动流程,不如说是springboot对spring程序的启动流程做了哪些更改。
其实不管是springboot程序还是spring程序,启动过程本质上都是在做容器的初始化,并将对应的bean初始化出来放入容器。在spring环境中,每个bean的初始化都要开发者自己添加设置,但是切换成springboot程序后,自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程,加上各种各样的参数设置,使得整体初始化过程显得略微复杂,但是核心本质还是在做一件事,初始化容器。作为开发者只要搞清楚springboot提供了哪些参数设置的环节,同时初始化容器的过程中都做了哪些事情就行了。
springboot初始化的参数根据参数的提供方,划分成如下3个大类,每个大类的参数又被封装了各种各样的对象,具体如下:
  • 环境属性(Environment)

  • 系统配置(spring.factories)

  • 参数(Arguments、application.properties)

以下通过代码流向介绍了springboot程序启动时每一环节做的具体事情。

Springboot30StartupApplication【10】->SpringApplication.run(Springboot30StartupApplication.class, args);
SpringApplication【1332】->return run(new Class<?>[] { primarySource }, args);
SpringApplication【1343】->return new SpringApplication(primarySources).run(args);
SpringApplication【1343】->SpringApplication(primarySources)
# 加载各种配置信息,初始化各种配置对象
SpringApplication【266】->this(null, primarySources);
SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
SpringApplication【281】->this.resourceLoader = resourceLoader;
# 初始化资源加载器
SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
# 初始化配置类的类名信息(格式转换)
SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath();
# 确认当前容器加载的类型
SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
# 获取系统配置引导信息
SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
# 获取ApplicationContextInitializer.class对应的实例
SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
# 初始化监听器,对初始化过程及运行过程进行干预
SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass();
# 初始化了引导类类名信息,备用
SpringApplication【1343】->new SpringApplication(primarySources).run(args)
# 初始化容器,得到ApplicationContext对象
SpringApplication【323】->StopWatch stopWatch = new StopWatch();
# 设置计时器
SpringApplication【324】->stopWatch.start();
# 计时开始
SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext();
# 系统引导信息对应的上下文对象
SpringApplication【327】->configureHeadlessProperty();
# 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
java.awt.headless=true
SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args);
# 获取当前注册的所有监听器
SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass);
# 监听器执行了对应的操作步骤
SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
# 获取参数
SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
# 将前期读取的数据加载成了一个环境对象,用来描述信息
SpringApplication【333】->configureIgnoreBeanInfo(environment);
# 做了一个配置,备用
SpringApplication【334】->Banner printedBanner = printBanner(environment);
# 初始化logo
SpringApplication【335】->context = createApplicationContext();
# 创建容器对象,根据前期配置的容器类型进行判定并创建
SpringApplication【363】->context.setApplicationStartup(this.applicationStartup);
# 设置启动模式
SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
# 对容器进行设置,参数来源于前期的设定
SpringApplication【338】->refreshContext(context);
# 刷新容器环境
SpringApplication【339】->afterRefresh(context, applicationArguments);
# 刷新完毕后做后处理
SpringApplication【340】->stopWatch.stop();
# 计时结束
SpringApplication【341】->if (this.logStartupInfo) {
# 判定是否记录启动时间的日志
SpringApplication【342】->new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
# 创建日志对应的对象,输出日志信息,包含启动时间
SpringApplication【344】->listeners.started(context);
# 监听器执行了对应的操作步骤
SpringApplication【345】->callRunners(context, applicationArguments);
# 调用运行器
SpringApplication【353】->listeners.running(context);
# 监听器执行了对应的操作步骤

上述过程描述了springboot程序启动过程中做的所有的事情,这个时候好奇宝宝们就会提出一个问题。如果想干预springboot的启动过程,比如自定义一个数据库环境检测的程序,该如何将这个过程加入springboot的启动流程呢?

遇到这样的问题,大部分技术是这样设计的,设计若干个标准接口,对应程序中的所有标准过程。当你想干预某个过程时,实现接口就行了。例如spring技术中bean的生命周期管理就是采用标准接口进行的。

public class Abc implements InitializingBean, DisposableBean {
    public void destroy() throws Exception {
        //销毁操作
    }
    public void afterPropertiesSet() throws Exception {
        //初始化操作
    }
}

springboot启动过程由于存在着大量的过程阶段,如果设计接口就要设计十余个标准接口,这样对开发者不友好,同时整体过程管理分散,十余个过程各自为政,管理难度大,过程过于松散。那springboot如何解决这个问题呢?它采用了一种最原始的设计模式来解决这个问题,这就是监听器模式,使用监听器来解决这个问题。

springboot将自身的启动过程比喻成一个大的事件,该事件是由若干个小的事件组成的。

例如:

  • org.springframework.boot.context.event.ApplicationStartingEvent

    • 应用启动事件,在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent

  • org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent

    • 环境准备事件,当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent

  • org.springframework.boot.context.event.ApplicationContextInitializedEvent

    • 上下文初始化事件

  • org.springframework.boot.context.event.ApplicationPreparedEvent

    • 应用准备事件,在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent

  • org.springframework.context.event.ContextRefreshedEvent

    • 上下文刷新事件

  • org.springframework.boot.context.event.ApplicationStartedEvent

    • 应用启动完成事件,在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent

  • org.springframework.boot.context.event.ApplicationReadyEvent

    • 应用准备就绪事件,在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求

  • org.springframework.context.event.ContextClosedEvent(上下文关闭事件,对应容器关闭)

上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。

//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
    public void onApplicationEvent(ApplicationStartingEvent event) {
		//自定义事件处理逻辑
    }
}

按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。

总结

  1. springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器

  2. 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码

原理篇完结

原理篇到这里就要结束了,springboot2整套课程的基础篇、实用篇和原理篇就全部讲完了。至于后面的番外篇由于受B站视频上传总量不得超过200个视频的约束,番外篇的内容不会在当前课程中发布了,会重新定义一个课程继续发布,至于具体时间,暂时还无法给到各位小伙伴。

原理篇个人感觉略微有点偷懒,怎么说呢?学习原理篇需要的前置铺垫知识太多,比如最后一节讲到启动流程时,看到reflush方法时我就想现在在看这套课程的小伙伴是否真的懂这个过程呢?但是如果把这些东西都讲了,那估计要补充的知识就太多了,就是将spring的很多知识加入到这里面重新讲解了,会出现喧宾夺主的现象。很纠结,( ´•︵•` )

课程做到这里就要和各位小伙伴先say顾拜了,感谢各位小伙伴的支持,也欢迎各位小伙伴持续关注黑马程序员出品的各种视频教程。黑马程序员的每位老师做课程都是认真的,都是为了各位致力于IT研发事业的小伙伴能够学习之路上少遇沟沟坎坎,顺利到达成功的彼岸。

相关文章
热点文章
精彩视频
Tags

站点地图 在线访客: 今日访问量: 昨日访问量: 总访问量: