文章目录

  • 深入探索SpringApplication执行流程
  • SpringApplicationRunListener
  • ApplicationListener
  • ApplicationContextInitializer
  • CommandLineRunner

如果非说SpringBoot微框架提供了点儿自己特有的东西,在核心类层面(除了各种场景下的自动配置一站式插拔模块),也就是SpringApplication了。

SpringApplication将一个典型的Spring应用启动的流程“模板化”(这里是动词),在没有特殊需求的情况下,默认模板化后的执行流程就可以满足需求了;但有特殊需求也没有关系,SpringApplication在合适的流程结点开放了一系列不同类型的扩展点,我们可以通过这些扩展点对SpringBoot程序的启动和关闭过程进行扩展。

最“肤浅”的扩展或配置是SpringApplication通过一系列设置方法(setters)开发的定制方式,比如:

SpringApplication.run(DemoApplication.class,args);
SpringApplication bootstrp=new SpringApplication(Demo-Configuration.class);
bootstrap.setBanner(new Banner(){@Overridepublic void printBanner(Environment environment,Class<?> aClass ,PrintStream printStream){//比如打印一个我们喜欢的ASCII Arts字符画}
});
bootstrap.setBannerMode(Banner.Mode.CONSOLE);
//其它定制设置。。。
bootstrap.run(args);

上面代码中设置自定义Banner最简单的方式其实是把ASCII Art字符画放到一个资源文件,然后通过ResourceBanner来加载:bootstrap.setBanner(new ResourceBanner(new ClassPathResource("banner.txt)));

大部分情况下,SpringApplication已经提供了很好的默认设置,所以,我们不再对这些表层进行探究了,因为对表层之下的东西进行探究才是我们的最终目的。

深入探索SpringApplication执行流程

SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程答大体可以归纳入下:

  1. 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里首先需要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例run方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

    1. 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext) 来决定是否应该创建一个为Web应用会用的ApplicationContext类型,还是应该创建一个标准的Standalone应用使用的ApplicationContext类型。
    2. 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
    3. 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
    4. 推断并设置main方法的定义类
  2. SpringApplication实例初始化完成并完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先通过遍历执行所有通过SpringFactoriesLoader可以查到并加载的SpringApplicationRunListener,调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
  3. 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
  4. 遍历调用所有SpringApplicationRunListener的environmentPrepared()方法 ,告诉它们:“当前SpringBoot应用使用的Environment准备好咯!”。
  5. 如果SpringApplication的showBanner属性被设置为true,则打印banner(在SpingBoot 1.3.x 版本,这里应该是基于Banner.Mode决定banner的打印行为)。这一步的逻辑其实可以不关心,我认为唯一的用途就是“好玩”(Just For Fun)。
  6. 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。
  7. ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriresLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。
  8. 遍历调用所有SpringApplicationRunListener的contextPrepared()方法,通知它们:“SpringBoot应用使用的ApplicationContext准备好啦!”
  9. 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
  10. 遍历调用所有SpringApplicationRunListener的contextLoaded()方法,告知所有SpringApplicationRunListener,“ApplicationContext 装填完毕!”
  11. 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
  12. 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。
  13. 正常情况下,遍历执行SpringApplicationRunListener的finished()方法,告知它们:“搞定!”。(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)。

至此,一个完成的SpringBoot应用启动完毕!
整个过程看起来冗长无比,但其实很多都是一些事件通知的扩展点,如果我们将这些逻辑暂时忽略,那么,其实整个SpringBoot应用启动的逻辑就可以压缩到极其精简的几步,如下图所示:

前后对比我们就可以发现,其实SpringApplication提供的这些各类扩展点近乎“喧宾夺主”,占据了一个Spring应用启动逻辑的大部分“江山”,除了初始化并准备好ApplicationContext,剩下的大部分工作都是通过这些扩展点完成的,所以,我们有必要对各类扩展点进行剖析,以便在需要的时候信手拈来,为我所用。

SpringApplicationRunListener

SpringApplicationRunListener是一个只有SpringBoot应用的main方法执行过程中接收不同执行时点事件通知的监听者:

public interface SpringApplicationRunListener { void started(); void environmentPrepared( ConfigurableEnvironment environment); void contextPrepared( ConfigurableApplicationContext context); void contextLoaded( ConfigurableApplicationContext context); void finished( ConfigurableApplicationContext context, Throwable exception);
}

对于我们来说,基本没什么常见的场景需要自己实现一个SpringApplicationRunListener,即使SpringBoot默认也只实现了一个org.springframework.boot.context.event.EventPublishingRunListener,用于在SpringBoot启动的不同时点发布不同的应用事件类型(ApplicationEvent),如果有哪些ApplicationListener对这些应用事件感兴趣,则可以接收并处理。

假设我们真的有场景需要自定义一个SpringApplicationRunListener实现,那么有一点需要注意,即任何一个SpringApplicationRunListener实现类的构造方法(Constructor)需要有两个构造参数,一个构造参数的类型就是我们的org.springframework.boot.SpringApplication,另外一个就是args参数列表的String[]:

public class DemoSpringApplicationRunListener implements SpringApplicationRunListener { @Override public void started() {// do whatever you want to do } @Override public void environmentPrepared( ConfigurableEnvironment environment) { // do whatever you want to do } @Override public void contextPrepared( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void contextLoaded( ConfigurableApplicationContext context) { // do whatever you want to do } @Override public void finished( ConfigurableApplicationContext context, Throwable exception) { // do whatever you want to do }
}

之后,我们可以通过SpringFactoriesLoader立下的规矩,在当前SpringBoot应用的classpath下的META-INF/spring.factories文件中进行类似如下的配置:

org.springframework.boot.SpringApplicationRunListener=\
com.self.springboot.demo.DemoSpringApplicationRunListener

然后SpringApplication就会在运行的时候调用它啦!

ApplicationListener

ApplicationListener其实是老面孔,属于Spring框架对Java中实现的监听者模式的一种框架实现,这里唯一值得着重强调的是,对于初次接触SpringBoot,但对Spring框架本身又没有过多接触的开发者来说,可能会将这个名字与SpringApplicationRunListener混淆。

关于ApplicationListener我们就不做过多介绍了,如果感兴趣,请参考Spring框架相关的资料和书籍。

如果我们要为SpringBoot应用添加自定义的ApplicationListener,有两种方式:

  1. 通过SpringApplication.addListeners(… )或者SpringApplication.setListeners(… )方法添加一个或者多个自定义的ApplicationListener;

  2. 借助SpringFactoriesLoader机制,在META-INF/spring.factories文件中添加配置(以下代码是为SpringBoot默认注册的ApplicationListener配置)

    org.springframework.context.ApplicationListener=\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.cloudfoundry.VcapApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicat- ionListener,\
    org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.logging.LoggingApplicationListener
    

关于ApplicationListener,我们就说这些。

ApplicationContextInitializer

ApplicationContextInitializer也是Spring框架原有的概念,这个类的主要目的是在ConfigurableApplictaionContext类型(或者子类型)的ApplicationContext的refresh之前,允许我们对ConfigurableApplicationContext的实例做进一步的设置和处理。

实现一个ApplicationContextInitializer很简单,因为它只有一个方法需要实现:

public class DemoApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize( ConfigurableApplicationContext applicationContext) { // do whatever you want with applicationContext, // e. g. applicationContext. registerShutdownHook();}
}

不过,一般情况下我们基本不会需要自定义一个ApplicationContextInitializer,即使SpringBoot框架默认也只是注册了三个实现:

org. springframework. context. ApplicationContextInitializer=\
org. springframework. boot. context. ConfigurationWarningsApplication- ContextInitializer,\
org. springframework. boot. context. ContextIdApplicationContextInitia- lizer,\
org. springframework. boot. context. config. DelegatingApplicationContex- tInitializer

如果我们真的需要自定义一个ApplicationContextInitializer,那么只要像上面这样,通过SpringFactoriesLoader机制进行配置,或者通过SpringApplication.addInitializers(…) 设置即可。

CommandLineRunner

CommandLineRunner不是Spring框架原有的“宝贝”,它属于SpringBoot应用特定的回调扩展接口:

public interface CommandLineRunner { void run( String... args) throws Exception;
}

CommandLineRunner需要大家关注的其实就两点:

  1. 所有CommandLineRunner的执行时点在SpringBoot应用的ApplicationContext完全初始化开始工作之后(可以认为是main方法执行完成之前最后一步)
  2. 只要存在于当前SpringBoot应用的ApplicationContext中的任何CommandLineRunner,都会被加载执行(不管你是手动注册这个CommandLineRunner到IoC容器,还是自动扫描进去的)

与其他几个扩展点接口类型相似,建议CommandLineRunner的实现类使用@org.springframework.core.annotation.Order进行标注或者实现org.springframework.core.Ordered接口,便于对它们的执行顺序进行调整,这其实十分重要,我们不希望顺序不当的CommandLineRunner实现类阻塞了后面其他CommandLineRunner的执行。

CommandLineRunner是很好的扩展接口,大家可以重点关注,我们在后面的扩展和微服务实践章节会再次遇到它。

Springboot的工作机制:3 SpringApplication:SpringBoot程序启动的一站式解决方案相关推荐

  1. SpringApplication:SpringBoot程序启动的一站式解决方案

    我们说SpringBoot是Spring框架对"约定优先于配置(Convention Over Configuration)"理念的最佳实践的产物,一个典型的SpringBoot应 ...

  2. 未能加载文件或程序集XXX.dll,程序启动失败的解决方案

    之前在VS2019上下载一个项目,运行后就报错,弹出 出错:未能加载文件或程序集XXX.dll 我们选中该dll,右键,属性,发现 该dll被锁定 点击"解决锁定"即可解决. 为了 ...

  3. Hadoop之Yarn工作机制详解

    Hadoop之Yarn工作机制详解 目录 Yarn概述 Yarn基本架构 Yarn工作机制 作业提交全过程详解 1. Yarn概述 Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于 ...

  4. finclip小程序运行机制与微信小程序运行机制

    根据运行小程序的宿主应用环境不同,小程序也会有不同的启动机制. #1. 小程序启动 小程序会有两种情况,一种是冷启动,一种是热启动. 冷启动:用户首次打开或小程序被宿主应用主动销毁后再次打开的情况,此 ...

  5. Hadoop生态圈(十三)- Namenode元数据管理及各组件工作机制

    目录 前言 1. Namenode元数据管理 1.1 元数据是什么 1.2 元数据管理概述 1.2.1 内存元数据 1.2.2 磁盘元数据 1.2.2.1 fsimage内存镜像文件 1.2.2.2 ...

  6. 简明扼要的HDFS元数据管理机制描述(NameNode和Secondary NameNode工作机制)

    目录 一.思考: NameNode中的元数据是存储在哪里? 二.NameNode和Secondary NameNode工作机制 三.Fsimage和Edits概念 一.思考: NameNode中的元数 ...

  7. 图文详解 HDFS 的工作机制及其原理

    大家好,我是大D. 今天开始给大家分享关于大数据入门技术栈--Hadoop的学习内容. 初识 Hadoop 为了解决大数据中海量数据的存储与计算问题,Hadoop 提供了一套分布式系统基础架构,核心内 ...

  8. SpringBoot学习(二)探究Springboot启动机制

    引言: SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起探究一下SpringBoot的启动原 ...

  9. springboot starter工作原理_springboot基础知识集结,你get到了吗

    导读 首发于公众号:JAVA大贼船,原创不易,喜欢的读者可以关注一下哦!一个分享java学习资源,实战经验和技术文章的公众号! 一.SpringBoot的特点 Spring Boot 主要目标是: 为 ...

  10. SpringBoot 服务监控机制,你了解多少?

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/zwx900102/article/ details/115446997 前言 任何一个服务如果没有监控,那就是两眼一抹黑,无法 ...

最新文章

  1. 牛顿的另一面:夺权、严惩罪犯,以一己之力挽救英国危机
  2. 前端如何高效的与后端协作开发
  3. 语言 重量计算_R语言 第五章 高级绘图工具(4)
  4. php编写服务器端脚本程序,PHP脚本语言写的简单服务器程序
  5. 《Unit Testing》2.1 经典学派如何做测试隔离
  6. 试用Mono Beta 1.0
  7. 印度18岁天才少年,造出“全球最小卫星”,实力不容小觑!
  8. onvif device manager 找不到ipc_常见网络摄像机IP搜索不到可能导致的问题及解决办法汇总...
  9. ERROR 2006 (HY000) MySQL server has gone away
  10. 《人工智能及其应用》1-6章
  11. java Servlet Filter 拦截Ajax请求
  12. Study From Work(2011-3-2)
  13. 春节过后,外贸人如何快速抓住采购旺季,高效跟进客户
  14. Axure 9.0.0.3704 授权码
  15. GPS北斗卫星时钟同步系统的原理和技术
  16. 阿里云国际版免费试用:如何注册以及注意事项
  17. AVD的CPU的选择
  18. SLAM在机器人中的应用
  19. 推荐系统实战 --- 基于音乐播放推荐
  20. MySQL数据库实际应用中,需求分析阶段需要做什么?

热门文章

  1. Java IO流常用操作方法总结
  2. Nacos整合SpringCloud的自动注册原理
  3. 双目视觉图像的色彩调整
  4. 原来 Python 还有这些实用的功能和特点!
  5. ylbtech-dbs:ylbtech-7,welfareSystem(福利发放系统)
  6. 关于rnn神经网络的loss函数的一些思考
  7. 文件的查找与压缩归档
  8. 磁盘设置压缩导致无法将数据库还原到该硬盘的问题
  9. Hibernate N+1 问题
  10. XML配置STS(编写Spring配置文件时,标签无自动提示符解决)