一、概述

starting作为springboot启动流程中最早的一个生命周期,过程相对比较简洁因此源码也相对较少,比较适合入门者的源码研究。在此阶段中spring主要完成了日志系统的选择、后台预加载等动作。

二、SpringApplicationRunListener和ApplicationListener

在上一篇文章中的最后一小节,我们可以发现在进行第一个生命周期回调(也就是starting)前,spring先通过getRunListeners方法获取了一个SpringApplicationRunListeners对象,可是通过下面源码发现这个对象并不是ApplicationListener的集合而是SpringApplicationRunListener的集合(虽然factories文件中默认只定义了一个类EventPublishingRunListener),那么这两者究竟有啥区别又有啥关联呢?

// org.springframework.boot.SpringApplication#getRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

细心的读者可能已经发现在SpringApplication的构造方法中已经通过setListeners将ApplicationListener注册进来了,从某种意义上来说后来构造的SpringApplicationRunListener其实是ApplicationListener的一个大管家。为什么这么说?来看下源码便一目了然。

// org.springframework.boot.context.event.EventPublishingRunListener
private final SpringApplication application;private final String[] args;private final SimpleApplicationEventMulticaster initialMulticaster;public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.initialMulticaster = new SimpleApplicationEventMulticaster();// 关键代码——将SpringApplication的ApplicationListener包含进来for (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}
}@Override
public void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}// 省略其他生命周期方法...

EventPublishingRunListener作为SpringApplicationRunListener的默认唯一实现,在构造的时候就将springApplication对象中的ApplicationListener添加进来并进行统一管理。从类的命名可以看出,这个类就是在观察者模式的基础上实现发布订阅,当启动流程执行到某一个阶段后,该类会生成一个生命周期事件(该事件类继承于java.util.EventObject),并将这个事件广播给已经注册的ApplicationListener,从而执行对应的钩子方法。感兴趣的读者可以看下这个类的完整源码,springboot的每一个生命周期都对应了该类的一个方法。

三、事件广播

通过上面代码可以发现有一个类也非常关键,那就是SimpleApplicationEventMulticaster,如果说SpringApplicationRunListener是一个大管家的话,那么SimpleApplicationEventMulticaster则是真正传话干活的人,spring的观察者们直接打交道的对象就是它。在每个生命周期节点,大管家也是调用它的multicastEvent方法进行事件发布。

通过下面源码可以看出multicastEvent方法其实就是遍历ApplicationListener对象,并进行钩子方法的回调。不过有一点值得一提的是getApplicationListeners也并非返回所有的ApplicationListener,而是会根据当前生命周期来进行过滤并排序,过滤的方式有两种:一种是利用反射获取Listener类声明的ApplicationEvent泛型;还有一种是根据观察者的supportsEventType方法的返回值(布尔类型)来判断。详细的过程可以参考AbstractApplicationEventMulticaster的supportsEvent方法,这里就不多展开。

// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {// 相当于最后调用了listener.onApplicationEvent(event);invokeListener(listener, event);}}
}

四、观察者们

在starting这个生命周期中,getApplicationListeners方法最终会过滤出如下几个ApplicationListener。其中第二个和第四个什么也没干所以忽略不计,第三个是我在factories文件中自己定义的观察者,仅仅只是打印了一句话而已。所以在starting中我们重点关注的观察者还是前两个。他们分别用于选择日志框架,以及将一些耗时的初始化在后台进行。

1. LoggingApplicationListener

先来看下日志观察者在启动时的源码,它主要执行了两个步骤:第一个是扫描类路径选择日志框架,第二个是预初始化日志框架

// org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent
private void onApplicationStartingEvent(ApplicationStartingEvent event) {this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());this.loggingSystem.beforeInitialize();
}

选择日志框架主要是在LoggingSystem的工厂方法get中完成的,可以发现在这个类初始化的时候会定义一个有序的Map,第一顺位是logback日志框架,第二顺位是log4j,最后一个是JDK日志。spring首先会过滤出类路径下存在的这几个框架,若存在多个则找到第一个。寻找完毕后通过反射的方式将对应的LoggingSystem实例化。

// org.springframework.boot.logging.LoggingSystem
static {Map<String, String> systems = new LinkedHashMap<>();// key是具体日志框架的关键类,value是spring的适配类systems.put("ch.qos.logback.core.Appender","org.springframework.boot.logging.logback.LogbackLoggingSystem");systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory","org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");systems.put("java.util.logging.LogManager","org.springframework.boot.logging.java.JavaLoggingSystem");SYSTEMS = Collections.unmodifiableMap(systems);
}
public static LoggingSystem get(ClassLoader classLoader) {String loggingSystem = System.getProperty(SYSTEM_PROPERTY);// 若JVM参数配置了日志系统,则使用该配置的框架if (StringUtils.hasLength(loggingSystem)) {if (NONE.equals(loggingSystem)) {return new NoOpLoggingSystem();}return get(classLoader, loggingSystem);}// 找出类路径下存在日志框架,并实例化对应的spring适配类return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)).map((entry) -> get(classLoader, entry.getValue())).findFirst().orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {try {Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);// spring的LoggingSystem类都有单classLoader参数的构造方法return (LoggingSystem) systemClass.getConstructor(ClassLoader.class).newInstance(classLoader);}catch (Exception ex) {throw new IllegalStateException(ex);}
}

选择完日志框架后还有个预初始化,对应的方法为beforeInitialize,这个过程主要会和底层的日志框架交互,所以就不过多展开,简单地看下流程。由于这个方法是个抽象方法,所以我以logback为例。getLoggerContext方法是获取logback的LoggerFactory;然后调用了父类的beforeInitialize方法,这个方法主要是配置一些BridgeHandler。

// org.springframework.boot.logging.logback.LogbackLoggingSystem#beforeInitialize
@Override
public void beforeInitialize() {LoggerContext loggerContext = getLoggerContext();if (isAlreadyInitialized(loggerContext)) {return;}super.beforeInitialize();loggerContext.getTurboFilterList().add(FILTER);
}

2. BackgroundPreinitializer

从这个类名中也可以推断它是用于在后台初始化某些对象。例如Jackson的objectMapper对象的创建、MessageConverter的创建等都是在这个对象的后台线程内完成的。源码中初始化的过程也是比较值得借鉴的,将一些目前无关紧要但是后续会经常用到的对象在一开始通过一个独立线程去完成,在某一阶段需要这些对象存在时可以通过CountDownLatch.await来确保初始化过程全部结束。

// org.springframework.boot.autoconfigure.BackgroundPreinitializer
@Override
public void onApplicationEvent(SpringApplicationEvent event) {/*满足三个条件开始后台初始化1. 没有配置JVM参数"spring.backgroundpreinitializer.ignore"2. 当前生命周期是starting3. 能够成功将初始化标志位设置为true(CAS的方式)*/if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore")&& event instanceof ApplicationStartingEvent&& preinitializationStarted.compareAndSet(false, true)) {// 启用一个线程进行一些初始化工作performPreinitialization();}if ((event instanceof ApplicationReadyEvent|| event instanceof ApplicationFailedEvent)&& preinitializationStarted.get()) {try {// 这个对象是CountDownLatch,若初始化在ready和failed阶段还未完成,则会等待初始化完毕preinitializationComplete.await();}catch (InterruptedException ex) {Thread.currentThread().interrupt();}}
}private void performPreinitialization() {try {// 单线程顺序执行下面的初始化器Thread thread = new Thread(new Runnable() {@Overridepublic void run() {runSafely(new ConversionServiceInitializer());runSafely(new ValidationInitializer());runSafely(new MessageConverterInitializer());runSafely(new MBeanFactoryInitializer());runSafely(new JacksonInitializer());runSafely(new CharsetInitializer());// 所有初始化器执行完毕则释放CountDownLatchpreinitializationComplete.countDown();}// 忽略异常public void runSafely(Runnable runnable) {try {runnable.run();}catch (Throwable ex) {// Ignore}}}, "background-preinit");thread.start();}catch (Exception ex) {// 创建或执行线程失败,释放CountDownLatchpreinitializationComplete.countDown();}
}

五、参考

spring-boot-2.0.3启动源码篇二 - run方法(一)之SpringApplicationRunListener

【springboot】启动流程之starting相关推荐

  1. 我的Android进阶修炼:安卓启动流程之init(1)

    文章目录 我的Android进阶修炼:安卓启动流程之init(1) 一.前言 二.init进程简介 1.文件位置 2.主要功能 三.init进程源码分析 3.1 main() 源码注解 3.1.1 参 ...

  2. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  3. springboot 启动过程之run

    上一篇文章学习了SpringApplication实例化的过程,这一篇来接着学习. 当springApplication对象创建成功后,将调用run(args)方法. SpringApplicatio ...

  4. android启动流程之lk,Android系统之LK启动流程分析(一)

    1.前言 LK是Little Kernel的缩写,在Qualcomm平台的Android系统中普遍采用LK作为bootloader,它是一个开源项目,LK是整个系统的引导部分,所以不是独立存在的,但是 ...

  5. Android平台WIFI启动流程之二

    http://blog.sina.com.cn/s/blog_13146f9590101wji1.html [摘要] 本文从用户界面出发,从应用层到硬件适配层,对Android平台wifi启动和关闭的 ...

  6. Linux启动流程之ROM-CODE

    1.从哪里开始? 下图是AM335X核心板和功能框图: AM335X核心板的存储信息如下: AM335X核心板运行linux系统,在这里提出一个问题: 上电后指令从哪里开始执行? DDR or EMM ...

  7. android启动流程之preloader--->lk

    关于异常的基本知识 什么是异常 对于AArch64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的 ...

  8. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  9. (连载)Android系统源码分析--Android系统启动流程之Linux内核

    > **这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 [github连载地址](https://github.com/foxleezh/AOSP/issues/3 ...

  10. (连载)Android 8.0 : 系统启动流程之Linux内核

    这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 github连载地址 前言 Android本质上就是一个基于Linux内核的操作系统,与Ubuntu Linux.Fedo ...

最新文章

  1. 最难忘的一天----一周年记
  2. 实例:使用puppeteer headless方式抓取JS网页
  3. MYECLIPSE中快速解决项目的错误的方法
  4. Nmap的高级扫描(脚本)
  5. antd table动态表头_React项目使用ant Table组件动态生成columns
  6. JavaFx loading 数据加载中效果
  7. java pdf转ofd
  8. FFmpeg连载2-分离视频和音频
  9. JavaScript开发必备!这四款静态代码分析工具你了解吗?
  10. 注册界面模板HTML+CSS
  11. Linux学习3 :用户及文件权限管理
  12. web网页本地视频播放器
  13. android 播放器音量,Android应用实例之调节播放器音量——AudioManager的应用
  14. 测绘-空中三角测量程序设计
  15. 安卓之位置服务(简单定位用户所在的位置)
  16. 纸壳CMS升级.Net5免费下载
  17. PS CC 2018 切片复制问题解决方法
  18. 【机器人学、机器视觉与控制】用工具箱确定D-H参数
  19. 网络优化整体解决方案 企业网络优化的好帮手
  20. rasa开发过程中出现的错误情况(实时更新)

热门文章

  1. 交通信号灯控制器C语言代码,交通信号灯控制器代码及说明.doc
  2. 制作u盘winpe启动盘_RUFUS.小巧的U盘启动盘制作工具
  3. genymotion模拟器安装app
  4. armv7l安卓刷linux,技术讲解-安卓APK快速生成后门(实现手机入侵)
  5. java 先入先出_一道java的基础题:一个线程安全的后进先出队列
  6. 域名和IP地址是一回事吗?建网站要买域名还要买IP地址吗?
  7. 微信PC版应用双开,一台计算机两个微信号
  8. Photoshop通道抠图
  9. 航拍南山区六个文化相关全景VR解读
  10. 阿里云对象存储OSS费用内容的说明