SpringApplication.run()的实现原理

SpringApplication.run()的实现原理。

return new SpringApplication(sources).run(args);


一,SpringApplication初始化


return new SpringApplication(sources)


第一步:设置系统参数默认值。

this.bannerMode = Mode.CONSOLE;

this.logStartupInfo = true;

this.addCommandLineProperties = true;

this.headless = true;

this.registerShutdownHook = true;


第二步:设置初始化器。一共6个。

this.setInitializers();


第三步:设置监听器。一共13个。

this.setLisreners();

这些监听器会一直监听Spring项目的启动流程,每当Spring容器状态发生改变,就会出发响应的事件。

这里重点说一下这些监听器的加载逻辑。

首先,拿到当前类的类加载器。其实就是AppClassLoader。

然后,去对应的文件中加载类。路径是META-INFO/spring.factories,这个路径相信大家不会陌生,如果我们想在Spring启动时加载我们自定义的Bean,那么我们就需要把Bean配置到这个文件中。

具体的加载实现可以参考SpringFactoryLoader,这个类实现的功能就是从META-INFO/spring.factories加载配置。


第四步:设置微服务启动类。启动入口。

this.mainApplicationClass = this.deduceMainApplicationClass();

加载启动类,会根据代码的堆栈调用关系,获取到StackTraceElement数组,然后根据main方法,找到main方法所在的class,这个class就是启动类。然后使用java反射机制加载启动类。

通常我们的微服务启动类是这样命名的:

XxxApplication.java

这是我们微服务的入口,就是在这个入口中调用SpringApplication.run来启动微服务的。


二,环境准备阶段


思考:bootstrap.properties如何加载到内存?


SpringApplication.run()执行的过程中会加载bootstrap.properties的配置项到内存。具体是执行下面这行代码来实现的:

ConfigurableEnvironment environment = this. prepareEnvironment(listeners,applicationArguments);

接下来,我们就分析一下这个环境变量的初始化流程。由于比较复杂,分步骤来说明。

prepareEnvironment(listeners,applicationArguments)方法进入以后。


第一步:this.getOrCreateEnvironment();

这行代码的功能就像它的名字,如果当前存在环境变量ConfigurableEnvironment,则直接返回。如果不存在,则新建,这里新建的是StandardServletEnvironment,然后强转成ConfigurableEnvironment返回。

StandardServletEnvironment继承了StandardEnvironment,主要包含4个系统属性:

servletConfigInitParams:断点调试无具体参数。

servletContextInitParams:断点调试无具体参数。

systemProperties:系统参数,59个。注意这59个参数,可以通过Java提供的System类直接获取到。如下:

System.getProperties("os.name");

systemEnvironment:系统参数,46个。


第二步:this.configEnvironment(environment,applicationArguments.getSourceArus())

配置环境变量,主要配置了2个属性,一个是defaultProperties,另一个是当前环境使用的配置文件activeProfiles。


第三步:listeners.environmentPrepared(environment)

这个步骤主要初始化5个属性:

bootstrap:这个属性主要是初始化spring.config.name,默认值bootstrap。

random:生成随机数,目前不知道干啥的。

applicationConfigurationProperties:解析微服务系统配置文件bootstrap.properties,解析以后生成的键值对就存放在这个对象中。

defaultProperties:这个属性有2个配置,

一个是spring. aop. proxyTargetClass,默认值为true,另一个是logging.pattern.level。

springCloudClientHostInfo:这个属性包含主机名和主机IP地址,对应的key分别为:spring.cloud.client.hostname和spring.cloud.client.ipAddress。


这一步的逻辑还是很复杂的,关键类是这个

SimpleApplicationEventMulticaster。使用的是事件机制,通过定时任务监听服务端的事件。


OK,环境准备阶段的工作基本就是这些。

最后我们来梳理一下环境准备阶段,初始化以后得到的环境对象,这个对象主要包含9个属性,就是上面分析的那10个属性:

bootstrapProperties:这个属性包含了配置中心配置的某个微服务下的xxx_sale.properties配置文件中的所有配置项。

servletConfigInitParams

servletContextInitParams

systemProperties

systemEnvironment

bootstrap

random

applicationConfigurationProperties:这个属性包含了application.properties配置文件中的所有配置项。

applicationConfig:属性包含了bootstrap.properties配置文件中的所有配置项。

defaultProperties

springCloudClientHostInfo



初始化系统环境的本质,其实就是对这些系统变量,系统参数进行初始化。


三,打印横幅


代码如下:

Banner printedBanner = printBanner(environment);

这行代码的功能就是打印横幅。什么是横幅呢?就是我们启动微服务时,控制台那个经典的“spring”。这个横幅是支持自定义修改的,方法也很简单,只需要在resource目录下添加banner.txt即可,控制台会打印banner.txt中的内容。


四,创建IOC容器


// 依据是否为web环境创建web容器或者普通的IOC容器

context = createApplicationContext();

analyzers = new FailureAnalyzers(context);

这里说明一下,web容器指的是AnnotationConfigEmbeddedWebApplicationContext,IOC容器指的是AnnotationConfigApplicationContext。我们微服务是web环境,所以创建的是IOC容器。AnnotationConfigApplicationContext容器的主要功能就是Bean的注册,所有的Bean都在这里完成注册。

这里加载AnnotationConfigApplicationContext类,使用的是反射机制,最后生成对象后强转成ConfigurableApplicationContext返回。

这里我们也来看看最后生成的这个context的结构。

读取器:AnnotatedBeanDefinitionReader

扫描器:ClassPathBeanDefinitionScanner

Bean工厂:DefaultListableBeanFactory

其他的属性,这里不再赘述,我们只关注这3个重要的属性。读取器使用的依然是默认的AnnotatedBeanDefinitionReader,但是扫描器变成了ClassPathBeanDefinitionScanner,这个和注解扫描器AnnotatedBeanDefinitionScanner有所不同。Bean工厂依然是我们熟悉的DefaultListableBeanFactory,这里面有存放Bean的容器beanDefinitionMap及其他所有必要的容器和参数。

经过创建环境阶段以后,Spring完成了以下内容:

1,创建读取器。

2,创建扫描器。

3,创建Bean工厂。

4,创建其他IOC容器的必要参数和属性。

总之,我们的Spring容器启动了。


五,准备上下文阶段


prepareContext(context, environment, listeners, applicationArguments,

printedBanner);

这一步概括来说,就是把一些系统启动相关的Bean注册到IOC容器中。


第一步:

context. setEnvironment(environment);

在ConfigurableApplicationContext中设置环境environment,这里设置的environment就是我们环境准备阶段初始化的environment。


第二步:

this. postProcessApplicationContext(context);

注册Bean:internalConfigurationBeanNameGenerstor。

设置资源加载器resourceLoader和类加载器classLoader。

// 执行Spring配置的初始化器

this.applyInitializers(context);

初始化器会根据配置,执行一些列的初始化操作,比如加载远程配置文件,调整配置项优先级等等。


第三步:

listeners.contextPrepared(context);

通知监听器,上下文准备就绪。


第四步:注册Bean

springApplicationArguments

springBootBanner


第五步:加载系统Bean和项目XML配置文件。


第六步:

listeners.contextLoaded(context);

通知监听器,上下文加载完成。


六,刷新上下文


this.refreshContext(context);

刷新容器,完成组件的扫描,创建,加载等。刷新以后还会注册一个钩子registerShutdownHook,作用是在Spring容器关闭后执行一些操作。


七,注册一些定时任务、监听器状态同步、关闭计时器等


afterRefresh(context, applicationArguments);

listeners.finished(context, null);

通知监听器,Spring上下文启动完成。

stopWatch.stop();

关闭计时器。

return context;

返回Spring上下文,至此,SpringApplication.run()方法执行结束。


OK,啰啰嗦嗦一大堆,相信大家也晕了,下面总结一下SpringApplication.run()的实现原理。

1,初始化SpringApplication。

设置一些系统参数,6个初始化器,13个监听器,设置main方法入口。

2,环境准备阶段。

初始化环境变量及其他系统参数,为启动做准备。这些参数有些是从jvm获取,有些是从项目的properties文件获取。

3,打印横幅。

4,创建IOC容器。

创建读取器。

创建扫描器。

创建Bean工厂。

创建其他IOC容器的必要参数和属性。

总之,Spring的IOC容器创建好了。

5,准备上下文阶段。

把之前准备的环境参数设置到IOC容器,并注册系统启动相关Bean到IOC容器。设置IOC容器的其他必要参数。

6,刷新上下文。

7,启动定时任务,监听器状态同步,关闭计时器。


纯手工断点调试写的,难免有疏漏,请大家多多指正。

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

推荐阅读更多精彩内容