P1-1 springboot启动原理

内容简介:介绍springboot的启动原理

相关链接

背景:SpringBoot 2.4

一、启动文件

SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,创建springboot项目之后会生成xxxApplication.java文件,该文件就是启动文件


run方法就是创建个spring容器,然后创建个web容器(tomcat,jetty等)启动.

让我们看到run方法是如何实现的


又调用了另一个run方法


这一步就是创建了SpringApplication对象并且执行了run方法SpringApplication实例化

 二、SpringApplication实例化


从上图SpringApplication实例化源代码中可以看出,在SpringApplication实例初始化的时候,它主要做这几件事事情:

    (1)-根据classpath里面是否存在某个特征类ConfigurableWebApplicationContext来决定是否应该创建一个为Web应用使用的ApplicationContext类型。

    (2)-使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的 ApplicationContextInitializer。

    (3)-使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。

    (4)-推断并设置main方法的定义类。

下面来看下具体的代码。

1-环境判断的deduceWebEnvironment()方法:


这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。

2-加载文件getSpringFactoriesInstances方法:

getSpringFactoriesInstances方法会加载META-INF/spring.factories文件,spring.factories文件中的默认的实现类,此处涉及两个类ApplicationListeners和ApplicationContextInitializer。

    (1) ApplicationListener可以监听某个事件event,通过实现这个接口,传入一个泛型事件,在run方法中就可以监听这个事件,从而做出一定的逻辑

    (2)ApplicationContextInitializer是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处理自定义逻辑。


 二、SpringApplication.run方法

Spring Boot应用的整个启动流程都封装在SpringApplication.run方法中,本质上其实就是在spring的基础之上做了封装,做了大量的扩张。我们看下其源码


该方法中实现了如下几个关键步骤:

    1.创建了应用的监听器SpringApplicationRunListeners并开始监听

    2.加载SpringBoot配置环境(ConfigurableEnvironment),

    3.根据是否是web项目,来创建不同的ApplicationContext容器

    4.实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误,

  5.准备容器prepareContext方法将listeners、environment、banner、applicationArguments等重要组件与上下文对象关联

   6.刷新容器,refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。

    7.刷新容器后的扩展接口

    回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成,接下来我们来探讨具体实现过程。

(一)获取并开启监听器

1-获取监听器getRunListeners方法

这个方法就是去拿到所有的SpringApplicationRunListener实现类,用于SpringBoot事件发布的,启动的时候会先加载spring会加载所有jar包下的META-INF/spring.factories,然后缓存起来


1.1-其中用到了getSpringFactoriesInstances方法:

该方法是获取spring.factories对应的监听器,其在SpringApplication的构造方法中调用了两次,分别用来设置属性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners。getSpringFactoriesInstances在第一次被调用时会将类路径下所有的META-INF/spring.factories的文件中的属性进行加载并缓存到SpringFactoriesLoader的缓存cache中,下次被调用的时候就直接从SpringFactoriesLoader的cache中取数据了。这次就是从SpringFactoriesLoader的cache中取SpringApplicationRunListener类型的类(全限定名),然后实例化后返回。我们来跟下这次getSpringFactoriesInstances获取的的内容


我们进去看一下getSpringFactoriesInstances方法


1.2-上面通过反射获取实例时会触发EventPublishingRunListener的构造函数:



1.3重点来看一下其中的addApplicationListener方法:


EventPublishingRunListener的构造方法中,构造了一个SimpleApplicationEventMulticaster对象,并将SpringApplication的listeners中的全部listener赋值到SimpleApplicationEventMulticaster对象的属性defaultRetriever(类型是ListenerRetriever)的applicationListeners集合中,继承关系为:


2-启动监听器starting方法

进入starting方法,可以看出它对监听器进行了遍历


2-1进去listener.starting方法

获取的监听器为EventPublishingRunListener,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。


从上图可以看出构建了一个ApplicationStartingEvent事件,并将其发布出去,其中调用了resolveDefaultEventType方法,该方法返回了一个封装了事件的默认类型(ApplicationStartingEvent)的ResolvableType对象。我们接着往下看,看看这个发布过程做了些什么。

2-2 multicastEvent方法


这里会根据事件类型ApplicationStartingEvent遍历getApplicationListeners(event, type)获取对应的监听器,在容器启动之后执行响应的动作,对每个listener进行invokeListener(listener, event)。

2-2-1getApplicationListeners方法

其作用是:返回与给定事件类型匹配的ApplicationListeners集合,非匹配的侦听器会被提前排除;允许根据缓存的匹配结果来返回。


getApplicationListeners方法主要以下3点:

缓存retrieverCache、retrieveApplicationListeners以及retrieveApplicationListeners中调用的supportsEvent方法。流程是这样的:

   (1)缓存中是否有匹配的结果,有则返回

   (2)若缓存中没有匹配的结果,则从this.defaultRetriever.applicationListeners中过滤,这个this表示的EventPublishingRunListener对象的属性initialMulticaster(即SimpleApplicationEventMulticaster对象,而defaultRetriever.applicationListeners的值也是在EventPublishingRunListener构造方法中初始化的)

 (3)过滤过程,遍历defaultRetriever.applicationListeners集合,从中找出ApplicationStartingEvent匹配的listener,具体的匹配规则需要看各个listener的supportsEventType方法(有两个重载的方法)

  (4)将过滤的结果缓存到retrieverCache

  (5)将过滤出的结果返回回去

过滤出的listener对象有以下几种


2-2-2 invokeListener方法

其作用是:使用给定的事件调用给定的监听器


2-3 onApplicationEvent方法

getApplicationListeners方法过滤出的监听器都会被调用,过滤出来的监听器包括LoggingApplicationListener、LiquibaseServiceLocatorApplicationListener、BackgroundPreinitializer、EnableEncryptablePropertiesBeanFactoryPostProcessor、DelegatingApplicationListener、五种类型的对象。这五个对象的onApplicationEvent都会被调用。根据发布的事件类型从上述监听器中选择对应的监听器进行事件发布,这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:



包含以下五种情况

    (1)在springboot启动的时候

    (2)springboot的Environment环境准备完成的时候

    (3)在springboot容器的环境设置完成以后

    (4)容器关闭的时候

    (5)容器启动失败的时候

(二)环境准备prepareEnvironment方法


2-1 getOrCreateEnvironment方法

前面已经提到,environment已经被设置了servlet类型,所以这里创建的是环境对象是StandardServletEnvironment。


在返回return new StandardServletEnvironment();对象的时候,会完成一系列初始化动作,主要就是将运行机器的系统变量和环境变量,加入到其父类AbstractEnvironment定义的对象MutablePropertySources中,MutablePropertySources对象中定义了一个属性集合:private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();

执行到这里,系统变量和环境变量已经被载入到配置文件的集合中,接下来就行解析项目中的配置文件。

2-2 listeners.environmentPrepared方法

进入该方法



这里是第二次发布事件:系统环境初始化完成的事件。发布事件的流程上面已经讲过了,这里不在赘述。

(三)创建容器createApplicationContext方法


进入该方法


上面可以看出,这里创建容器的类型 还是根据webApplicationType进行判断的,前文已经讲述了该变量如何赋值的过程。因为该类型为SERVLET类型,所以该容器的名称为AnnotationConfigServletWebServerApplicationContext

依赖关系如下图所示。


(四)准备容器prepareContext

这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础.


4-1postProcessApplicationContext方法

容器的post处理,子类可以根据需要申请额外处理。


这里默认不执行任何逻辑,因为beanNameGenerator和resourceLoader默认为空。之所以这样做,是springBoot留给我们的扩展处理方式,类似于这样的扩展,spring中也有很多。

4-2applyInitializers方法

将ApplicationContextInitializer应用到上下文


在 applyInitializers 中遍历调用每一个被加载的 ApplicationContextInitializer 的  initialize(context);  方法,并将 ConfigurableApplicationContext 的实例传递给 initialize 方法。

4-3 listeners.contextPrepared(context)

通知监听器,上下文准备好了,调用EventPublishingRunListener的contextPrepared,发现其是空实现,也就是相当于啥事也没做。

4-4 load加载启动类

这里会将我们的启动类加载spring容器beanDefinitionMap中,为后续springBoot 自动化配置奠定基础,springBoot为我们提供的各种注解配置也与此有关。



需要注意的是,springBoot2会优先选择groovy加载方式,找不到再选用java方式。或许groovy动态加载class文件的性能更胜一筹

4-5 listeners.contextLoaded(context)

通知监听器,容器已准备就绪

(五)刷新容器refreshContext(context)

执行到这里,springBoot相关的处理工作已经结束,接下的工作就交给了spring。

实际上Tomcat的启动也是在refresh流程中,这个方法其中一步是调用了onRefresh方法,在Spring中这是一个没有实现的模板方法,而SpringBoot就通过这个方法完成了Tomcat的启动:


这里首先拿到TomcatServletWebServerFactory对象,通过该对象再去创建和启动Tomcat:


一路跟进去,refresh方法在spring整个源码体系中举足轻重,是实现 ioc 和 aop的关键。上述流程,不是一篇博文能够展示清楚的,所以这里暂时不做展开。后续会有详细的介绍。

(六)刷新容器后的扩展接口refreshContext

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

至此springBoot2启动流程到这里就结束了,引用一张流程图。


上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分,第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器,第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块,第三部分是自动化配置模块,该模块作为springboot自动配置核心,

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容