SpringBoot启动原理分析

SpringBoot启动原理分析,第1张

自动配置核心类SpringFactoriesLoader

上面在说@EnableAutoConfiguration的时候有说META-INF下的springfactories文件,那么这个文件是怎么被spring加载到的呢,其实就是SpringFactoriesLoader类。

SpringFactoriesLoader是一个供Spring内部使用的通用工厂装载器,SpringFactoriesLoader里有两个方法

在这个SpringBoot应用启动过程中,SpringFactoriesLoader做了以下几件事:

加载所有META-INF/springfactories中的Initializer

加载所有META-INF/springfactories中的Listener

加载EnvironmentPostProcessor(允许在Spring应用构建之前定制环境配置)

接下来加载Properties和YAML的PropertySourceLoader(针对SpringBoot的两种配置文件的加载器)

各种异常情况的FailureAnalyzer(异常解释器)

加载SpringBoot内部实现的各种AutoConfiguration

模板引擎TemplateAvailabilityProvider(如Freemarker、Thymeleaf、Jsp、Velocity等)

总得来说,SpringFactoriesLoader和@EnableAutoConfiguration配合起来,整体功能就是查找springfactories文件,加载自动配置类。

整体启动流程

在我们执行入口类的main方法之后,运行SpringApplicationrun,后面new了一个SpringApplication对象,然后执行它的run方法。

初始化SpringApplication类

创建一个SpringApplication对象时,会调用它自己的initialize方法

执行核心run方法

初始化initialize方法执行完之后,会调用run方法,开始启动SpringBoot。

首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/springfactories中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的starting()方法,通知这些监听器SpringBoot应用启动。

创建并配置当前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。

遍历调用所有的SpringApplicationRunListeners的environmentPrepared()的方法,通知这些监听器SpringBoot应用的Environment已经完成初始化。

打印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在bannertxt文件,则打印其内容,否则打印默认banner。

根据启动时设置的applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。

创建异常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。

设置SpringBoot的Environment,注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。

调用所有的SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。

最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

调用所有的SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。

调用refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)

注: 钩子可以在以下几种场景中被调用:

1)程序正常退出

2)使用Systemexit()

3)终端使用Ctrl+C触发的中断

4)系统关闭

5)使用Kill pid命令杀死进程

获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法

遍历所有的SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。

以上是一个最简单的Springboot程序(203版本)示例,也是我们最通用的写法,但其中其实封装这一系列复杂的功能 *** 作,让我们开始逐步进行分析。

首先这里最重要的必然是注解 @SpringBootApplication

@SpringBootApplication 注解由几个注解复合组成,其中最主要的就是 @SpringBootConfiguration 、 @EnableAutoConfiguration 和 @ComponentScan 这三个。

其中的 @ComponentScan 是spring的原生注解, @SpringBootConfiguration 虽然是springboot中的注解,但其实质就是包装后的 @Configuration ,仍然是spring中的注解,用于代替xml的方式管理配置bean

@EnableAutoConfiguration 的定义如上,这里最重要的注解是 @Import ( @AutoConfigurationPackage 注解的实现也是基于 @Import ),借助 @Import 的帮助,将所有符合自动配置条件的bean定义加载到IoC容器中。关于 @EnableAutoConfiguration 注解后续涉及到时会再详细说明。这里我们先回到启动类的 run 方法从头分析初始化流程。

可以看到'run'方法最终调用的是 new SpringApplication(primarySources)run(args) ,这里首先创建了 SpringApplication 对象,然后调用其 run 方法

这里主要是为 SpringApplication 对象进行初始化,这里要专门提一下的是 webApplicationType 和 getSpringFactoriesInstances 。

它用来标识我们的应用是什么类型的应用,来看一下 deduceWebApplicationType() 方法的实现

其返回值是 WebApplicationType 类型的枚举类,其值有 NONE 、 SERVLET 、 REACTIVE 三种,分别对应非WEB应用,基于servlet的WEB应用和基于reactive的WEB应用。

这里的核心是 SpringFactoriesLoaderloadFactoryNames(type, classLoader) 方法,来看一下

重点关注一下 loadSpringFactories(classLoader) 做了什么

这里的 FACTORIES_RESOURCE_LOCATION 定义为 META-INF/springfactories ,因此该方法会扫描所有包下的该文件,将其解析成map对象并缓存到 cache 中以避免重复加载,springboot包下该文件的部分片段如下

从这里可以看出, setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializerclass)) 和 setListeners((Collection) getSpringFactoriesInstances(ApplicationListenerclass)); 分别对应设置的是上述这些类。

解析完成后调用 createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names) 处理解析结果,生成对应的实例,源码如下

这里的核心是通过 ClassUtilsforName(name, classLoader) 方法,以反射的方式生成类实例 instanceClass 。由此可以看出 SpringFactoriesLoaderloadFactoryNames(type, classLoader) 的作用就是将 META-INF/springfactories 中配置的内容进行实例化的工厂方法类,具备很强的扩展性,与SPI机制有异曲同工

的效果。

看完 SpringApplication 的初始化,接着跳回 run 方法继续分析

这里挑其中比较重要的几个方法进行分析

通过 getOrCreateEnvironment() 方法创建容器环境

可以看到 environment 存在则不会重复创建,当应用类型为servlet时创建的是 StandardServletEnvironment 对象,否则创建 StandardEnvironment 对象。

接着来看 configureEnvironment(environment, applicationArgumentsgetSourceArgs())

configurePropertySources(environment, args) 加载启动命令行的配置属性,来看一下实现

这里的 MutablePropertySources 对象用于存储配置集合,其内部维护了一个 CopyOnWriteArrayList 类型的list对象,当默认配置存在时,会向该list的尾部插入一个 new MapPropertySource("defaultProperties", thisdefaultProperties) 对象。

接着来看 configureProfiles(environment, args)

这里主要做的事情就是获取 environmentgetActiveProfiles() 的参数设置到 environment 中,即 springprofilesactive 对应的环境变量。

最后来看一下 listenersenvironmentPrepared(environment)

这里的 listeners 就是之前通过 META-INF/springfactories 注册的所有listeners,后面我们先以其中最重要的 ConfigFileApplicationListener 做为例子进行分析,接着来看 listenerenvironmentPrepared(environment)

可以看到这里创建了一个 ApplicationEnvironmentPreparedEvent 类型的事件,并且调用了 multicastEvent 方法,通过该方法最终会调用到listener的 onApplicationEvent 方法,触发事件监听器的执行。

接下来具体看一下 ConfigFileApplicationListener 的 onApplicationEvent 方法做了什么

可以看到当监听到 ApplicationEnvironmentPreparedEvent 类型的事件时,调用 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event) 方法

可以看到这里通过 loadPostProcessors() 方法加载了 META-INF/springfactories 中的所有 EnvironmentPostProcessor 类到list中,同时把 ConfigFileApplicationListener 自己也添加进去了。接着遍历list中所有对象,并执行 postProcessEnvironment 方法,于是接着来看该方法

这里的核心是 new Loader(environment, resourceLoader)load() ,这里的 Loader 是一个内部类,用于处理配置文件的加载,首先看一下其构造方法

可以看到这里的 resourceLoader 又是通过 SpringFactoriesLoader 进行加载,那么来看看 META-INF/springfactories 中定义了哪些 resourceLoader

从名字就可以看出来, PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 分别用于处理properties和yml类型的配置文件。

接着来看看 load() 方法做了什么

initializeProfiles() 进行了 profiles 的初始化,默认会添加 null 和 default 到 profiles 中, null 对应配置文件applicationproperties和applicationyml, default 对应配置文件application-defaultyml和application-defaultproperties,这里的 null 会被优先处理,由于后处理的会覆盖先处理的,因此其优先级最低。

接着来看 load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)) 方法

这里重点是通过 getSearchLocations() 获取配置文件的路径,默认会获得4个路径

接着会遍历这些路径,拼接配置文件名称,选择合适的yml或者properties解析器进行解析,最后将结果添加到 environment 的 propertySources 中。

可以看到这里也是根据 webApplicationType 的取值,分别创建不同的返回类型。

这里的 sources 装的就是我们的启动类,然后通过 load(context, sourcestoArray(new Object[0])) 方法进行加载

来看一下 loader 是如何被加载的

经过一系列调用之后最终由 load(Class<> source) 方法执行,这里比较有趣的是当Groovy存在时居然是优先调用Groovy的方式进行加载,否则才走 thisannotatedReaderregister(source) 方法将启动类注册到 beanDefinitionMap 中。

这个 refresh() 方法相当重要,尤其是 invokeBeanFactoryPostProcessors(beanFactory) ,这是实现spring-boot-starter-(mybatis、redis等)自动化配置的关键部分,后续再详细讲解。

至此Springboot的启动流程已经大体分析完了,也了解了配置文件和启动类分别是是如何被加载的,但仍有两个问题待解,一是Springboot的核心思想约定大于配置是如何做到的,二是Springboot的各种spring-boot-starter-是如何发挥作用的,这两个问题留待后续文章继续分析。

初始化:

11 调 SpringFactoriesLoader#getSpringFactoriesInstances 方法,key为:SpringApplicationRunListenerclass,并创建对应的实例。

SpringApplicationRunListener负责在springboot启动的不同阶段,广播出不同的消息,传递给ApplicationListener实现类。

12 把11中获取的监听器对象遍历starting()。

应用上下文环境:就是指一个环境的集合,包含多部分的环境信息。例如:系统信息、jdk环境信息、自定义信息等。

把所有的环境信息进行加载封装到environment对象中,使用时候直接取。

21 创建并配置相应的环境;

根据应用不同,创建需要的应用环境。

22 根据用户配置,配置environment系统环境;

例如:开发环境/生产环境/测试环境有不同的配置文件,加载配置文件,封装成 SimpleCommandLinePropertySource 加入到环境中。

23 启动相应的监听器,其中有一个重要的监听器 configFileApplicationListener (项目配置文件的监听器)

configFileApplicationListener 监听器是run方法初始化中第二滴根据ApplicationListenerclass获取的监听器中的一个

应用上下文:当前环境的属性集合 ;

可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一

些高级功能。

应用上下文对IoC容器是持有的关系。他的一个属性beanFactory就是IoC容器

(DefaultListableBeanFactory)。所以他们之间是持有,和扩展的关系。

返回值赋值给开始定义的ConfigurableApplicationContext

在createApplicationContext()方法中的,BeanUtilsinstantiateClass(contextClass) 这个方法中,不但初始化了AnnotationConfigServletWebServerApplicationContext类,也就是我们的上下文context,同样

也触发了GenericApplicationContext类的构造函数,从而IoC容器也创建了。

看他的构造函数,发现一个很熟悉的类DefaultListableBeanFactory(是IoC容器)

这步的核心就是对第三步获得的上下文对象进行属性的设置和一些bean对象的创建。例如我们的核心启动类。

SpringBoot中有三种实现定位,

所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:

classpath:com/lidongz//class这样的形式,然后一个叫做

xPathMatchingResourcePatternResolver的类会将该路径下所有的class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition

通过基础路径的扫描,确定@Import注解需要加载的类,调用类中的方法 从 META-INF/springfactories 中获取的全路径类名,完成BeanDefinition的加载和注册。

获取到的权限定类名中有标注的@bean的方法也会执行,从而再创建一些bean对象存到容器中。

怎么扫描启动类上的解析、

compenentScan不配置路径为什么会是核心类所在的包以及子包

解析compenentScan注解的时候 如果值为null 给一个值就是核心类所在的包,然后doscan扫描就是以这个为基础路径

SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本非常低,但是学习其实现原理的成本大大增加,需要先了解熟悉Spring原理。

如果还不清楚Spring原理的,可以先查看博主之前的文章,本篇主要分析SpringBoot的启动、自动配置、Condition、事件驱动原理。

SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:

可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注 @SpringBootApplication 注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?

在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个 ApplicationContext 的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的 *** 作。

另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:

SpringBoot的启动流程就是这个方法,先看 getRunListeners 方法,这个方法就是去拿到所有的 SpringApplicationRunListener 实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:

一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从 META-INF/springfactories 文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。

为什么要这样做呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过 @Import 注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。

回到run方法中,可以看到调用了 createApplicationContext 方法,见名知意,这个就是去创建应用上下文对象:

注意这里通过反射实例化了一个新的没见过的上下文对象 AnnotationConfigServletWebServerApplicationContext ,这个是SpringBoot扩展的,看看其构造方法:

如果你有看过Spring注解驱动的实现原理,这两个对象肯定不会陌生,一个实支持注解解析的,另外一个是扫描包用的。

上下文创建好了,下一步自然就是调用refresh方法启动容器:

这里首先会调用到其父类中 ServletWebServerApplicationContext :

可以看到是直接委托给了父类:

这个方法不会陌生吧,之前已经分析过了,这里不再赘述,至此SpringBoot的容器就启动了,但是Tomcat启动是在哪里呢?run方法中也没有看到。

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

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

上面的每一步都可以对比Tomcat的配置文件,需要注意默认只支持了>

以上就是关于SpringBoot启动原理分析全部的内容,包括:SpringBoot启动原理分析、Springboot初始化流程解析、springboot随笔5.0:run方法执行流程等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/9573580.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存