【微服务专题】02-Spring Boot启动原理核心源码剖析 更详细的一篇文章,可以结合一起看
首先思考几个问题:
1.启动入口是哪个类?
2.配置文件或配置的属性是在哪加载的?如何定义优先级顺序的?
3.springboot下如果依赖多个模块,那么这些模块是如何被加载进来的,即扩展点具体怎么应用的?
答:在入口配置类上添加@SpringBootApplication标签,间接引用 @EnableAutoConfiguration,后者会扫描并加载扩展点下的EnableAutoConfiguration接口的实现类集合,这样实现了新的模块被加载
4.扩展点里面有很多接口,这些接口一般是何时被调用? 答案是 通过启动后在不同阶段出发不同的事件,存在很多监听器,监听到事件后会调用相应的接口
springboot采用ServletWebServerApplicationContext完成加载上下文,该类功能类似ClassPathXmlApplicationContext。
1.ApplicationStartingEvent在运行开始时发送,但在进行任何处理之前(侦听器和初始化程序的注册除外)发送。
2.在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent。
3.准备ApplicationContext并调用ApplicationContextInitializers之后,将发送ApplicationContextInitializedEvent。
4.读取完配置类后发送ApplicationPreparedEvent。
5.在刷新上下文之后但在调用任何应用程序和命令行运行程序之前,将发送ApplicationStartedEvent。
6.紧随其后发送带有LivenessState.CORRECT的AvailabilityChangeEvent,以指示该应用程序被视为处于活动状态。
7.在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。
8.紧随其后发送ReadabilityState.ACCEPTING_TRAFFIC的AvailabilityChangeEvent,以指示应用程序已准备就绪,可以处理请求。
如果启动时发生异常,则发送ApplicationFailedEvent。
用过springboot的技术人员很显而易见的两者之间的差别就是视觉上很直观的:springboot有自己独立的启动类(独立程序),调用静态方法run():
@SpringBootApplication // 标记成Springboot的启动类
public class Application {
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringApplication.class,args);
}
内部new了一个SpringApplication实例,并调用实例上的run()方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
分2步,创建SpringApplication实例和执行SpringApplication实例
看下构造函数:
new SpringApplication(primarySources)
进一步调用重载的构造函数:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// [1]将启动类放入primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// [2]根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// [3]就是去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//[4]就是去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// [5]根据main方法推算出mainApplicationClass
this.mainApplicationClass = deduceMainApplicationClass();
}
[3]和[4] 很重要,其实就是去jar和classpath中去查找spring.factories下的扩展点:
META-INF/spring.factories扩展点原理可参见 《自动配置原理 1 加载自动配置类》和
【SpringBoot】扩展机制之Spring Factories
[3]处代码 此时会加载实现了 org.springframework.context.ApplicationContextInitializer接口的扩展点(该接口的实现类):
[4]处代码 此时会加载实现了org.springframework.context.ApplicationListener接口的扩展点(该接口的实现类):
就是去初始化了一些信息
启动springboot最核心的逻辑
public ConfigurableApplicationContext run(String... args) {
// [1]用来记录当前springboot启动耗时
StopWatch stopWatch = new StopWatch();
// 就是记录了启动开始时间
stopWatch.start();
// 它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//[2] 开启了Headless模式:
configureHeadlessProperty();
// [3]去spring.factroies中读取了SpringApplicationRunListener 的组件, 就是用来发布事件或者运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//[3.1] 发布1.ApplicationStartingEvent事件,在运行开始时发送
listeners.starting();
try {
// 根据命令行参数 实例化一个ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//[4] 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// [5]忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// [6]打印Banner 横幅
Banner printedBanner = printBanner(environment);
// [7]根据webApplicationType创建Spring上下文
context = createApplicationContext();
//[7.0] 创建一系列FailureAnalyzer
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// [7.1]预初始化spring上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//[7.2] 加载spring ioc 容器 相当重要
//由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
//加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh
refreshContext(context);
//[7.3]查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
[3] 去spring.factroies中读取了SpringApplicationRunListener 的组件,对于具体的集合的元素,目前只有一个,即EventPublishingRunListener实例。
SpringApplicationRunListeners
其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。
[3.1] 循环调用starting()方法,发布ApplicationStartingEvent事件,时间点是在运行开始时。
回顾 <2.1 SpringBoot 事件监听器发布顺序>章节提到的事件顺序,ApplicationStartingEvent正是第一个事件,后续还会有其他的事件
[4] 预初始化环境
[6]打印Banner 横幅,当然我们自己也可以定制
[7] 创建一个ApplicationContext,对应AnnotationConfigServletWebServerApplicationContext类型的实例
看下SpringApplicationRunListeners对应一个集合,集合的内容均是SpringApplicationRunListener接口的子类,我们来看下该接口的源码:
public interface SpringApplicationRunListener {
// 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
void starting();
// Environment准备好后,并且ApplicationContext创建之前调用
void environmentPrepared(ConfigurableEnvironment environment);
// ApplicationContext创建好后立即调用
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext加载完成,在refresh之前调用
void contextLoaded(ConfigurableApplicationContext context);
// 当run方法结束之前调用
void finished(ConfigurableApplicationContext context, Throwable exception);
}
可以看到,该接口定义了多个方法,分别在不同的时间点被调用,由其子类实现,负责发送特定的事件
[3] 处代码对于具体的集合的元素,目前只有一个具体的子类,即EventPublishingRunListener
, 作用是发送ApplicationStartingEvent事件,由覆写的starting()实现,目的是告别别人“SpringBoot应用要开始执行了"。
我们看子类EventPublishingRunListener的源码:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
//负责发送ApplicationStartingEvent事件
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
[4] 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器),包括配置要使用的PropertySource以及Profile。
创建并配置当前应用将要使用的Environment,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//[1] 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量
//如果已存在Environment ,则直接返回
ConfigurableEnvironment environment = getOrCreateEnvironment();
// [2]将命令行参数读取环境变量中
configureEnvironment(environment, applicationArguments.getSourceArgs());
// [3]将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
ConfigurationPropertySources.attach(environment);
// [4]发布了ApplicationEnvironmentPreparedEvent 的监听器 读取了全局配置文件
listeners.environmentPrepared(environment);
// [5]将所有spring.main 开头的配置信息绑定SpringApplication
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//更新PropertySources
ConfigurationPropertySources.attach(environment);
return environment;
}
[1] 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量;如果已存在Environment ,则直接返回
如果是web项目就创建StandardServletEnvironment,否则创建StandardEnvironment
[2] 将命令行参数读取环境变量中,这样,就可以在环境变量中引用到命令行参数了。
[3] 将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
@PropertieSource优先级是最低,放在代码最靠前,后面加载的参数会覆盖前面的,详细的顺序可以参见 Spring Boot 配置优先级顺序
[4] 调用SpringApplicationRunListener的environmentPrepared()
方法,通知事件监听者:应用的Environment已经准备好。
当然,这里仍然是SpringApplicationRunListener的接口的子类EventPublishingRunListener负责实现,看下源码:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
此时是第二个事件
[5] 将所有spring.main 开头的配置信息绑定到 SpringApplication上
[7.1]预初始化spring上下文
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//[1] 将准备好的Environment设置给ApplicationContext
context.setEnvironment(environment);
postProcessApplicationContext(context);
//[2] 拿到之前读取到所有ApplicationContextInitializer的组件, 循环调用initialize方法
applyInitializers(context);
//[3] 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
//对应的事件是ApplicationContextInitializedEvent
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//[4] 获取当前spring上下文beanFactory (负责创建bean)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// [5]在Spring下 如果出现2个重名的bean, 则后读取到的会覆盖前面
// 在SpringBoot 在这里设置了不允许覆盖, 当出现2个重名的bean 会抛出异常
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// [6]设置当前spring容器是不是要将所有的bean设置为懒加载
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//[7] 加载所有的类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 读取主启动类 (因为后续要根据配置类解析配置的所有bean)
load(context, sources.toArray(new Object[0]));
//[8]读取完配置类后发送ApplicationPreparedEvent。
listeners.contextLoaded(context);
}
[1] 将准备好的Environment设置给ApplicationContext
[2] 遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理
[3] 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
对应的事件是ApplicationContextInitializedEvent,是第三个事件
[7] 将所有的bean加载到容器中(这里所有bean是指带@SpringBootApplication的启动类,不是指配置@Controller或@Service这样的bean,后者是在下面的refreshContext()加载的)
[8] 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
具体的事件对应ApplicationPreparedEvent,对应第四个事件
refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作:
最终会调用父类AbstractApplicationContext
的refresh() 方法:
refresh()方法是不是似曾相识?对的,spring的启动流程也是相似的逻辑,参见《02.Ioc容器加载过程-Bean的生命周期源码深度剖析–spring笔记》中的<1.1.3 refresh()>章节
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
//[1] 处理 BeanFactoryPostProcessor 接口,此处会去加载自动配置类
invokeBeanFactoryPostProcessors(beanFactory);
//[2] 处理 BeanPostProcessor 接口
registerBeanPostProcessors(beanFactory);
// 初始hauler message source ,即国际化
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
....
}
}
[1] 很重要,处理 BeanFactoryPostProcessor 接口,此处会去加载自动配置类
最终会调用ConfigurationClassPostProcessor. processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法:
注意执行的步骤:
1. String[] candidateNames = registry.getBeanDefinitionNames();
2. parser.parse(candidates);
3. this.reader.loadBeanDefinitions(configClasses);
4. while循环处理candidateNames集合中定义的类,加载它们
步骤示意图,黄色区域是idea debug的调用栈,执行顺序从下往上,从main函数开始,层层调用到refresh()方法:
ConfigurationClassPostProcessor用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。具体代码对应parser.parse(candidates)
,当处理@import注解的时候,就会调用<自动配置>这一小节中的EnableAutoConfigurationImportSelector.selectImports()来完成自动配置功能
上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分:
参考:
《spring boot(二):启动原理解析》