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,启动定时任务,监听器状态同步,关闭计时器。
纯手工断点调试写的,难免有疏漏,请大家多多指正。