Springboot启动流程
方法入口: org.springframework.boot.SpringApplication#run(java.lang.String…)
-
StopWatch 开启,公开总运行时间和每个命名任务的运行时间
-
getRunListeners(args) : 获取SpringApplicationRunListener 监听者
-
prepareEnvironment(): 准备环境,并发送环境准备事件给相关listeners,这时候有ConfigFileApplicationListener时可以从配置文件读取environment,可通过EnvironmentPostProcessor处理环境参数
-
configureIgnoreBeanInfo() : spring.beaninfo.ignore配置
-
printBanner: 打印banner
-
createApplicationContext(): 根据应用类型新建 ConfigurableApplicationContext 容器
-
prepareContext: 设置环境变量,初始化容器,加入BeanDefinitionLoader用于后面载入BeanDefinition,然后载入一些系统和main方法主类的beanBeanDefinition
-
refreshContext: 刷新容器
-
afterRefresh , stopWatch.stop() 结束刷新上下文
listener相关事件: starting,environmentPrepared,contextPrepared, contextLoaded, started, running, failed, callFailedListener
在正在开启,启动完成,失败,环境准备,容器准备,容器载入完成时会通过容器事件回调。
- 注意点: 如果使用spring cloud那么会在环境准备environmentPrepared时,有BootstrapApplicationListener会监听该事件,并启动父容器,父容器名称默认为"bootstrap"
读取配置文件问题与详解
问题:springboot 父容器无法读取 spring子容器的配置文件中的参数值?
入口: org.springframework.boot.context.config.ConfigFileApplicationListener#postProcessEnvironment
- 通过参数spring.profiles.active/include 属性中初始化配置文件信息,可以通过环境变量来配置需要读取的文件,做不同开发环境的处理
- load 加载文件,相关代码
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
// getSearchLocations()是通过spring.config.location获取配置文件的路径去加载文件
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
// getSearchNames()是通过 spring.config.name获取配置文件的名称,默认为application
Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
- 父容器相关配置
入口: org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext
相关代码:
String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
答案:父容器默认读取配置文件 bootstrap.yml,也可以通过 ${spring.cloud.bootstrap.location:} 来添加修改读取配置文件的路径,可以解释为什么父容器无法读取spring中application.yml中的属性
ApplicationContext 容器refresh刷新详解
方法入口:org.springframework.context.support.AbstractApplicationContext#refresh
- 获取锁
-
prepareRefresh(): 准备刷新,校验环境,此处可以重写 initPropertySources对环境校验
-
obtainFreshBeanFactory(): 获取容器中的beanFactory,不存在就创建并初始化 BeanFactory, 存在则设置id
-
prepareBeanFactory(): 填充BeanFactory 一些属性,解析器,回调接口等等
-
postProcessBeanFactory(): 允许上下文子类容器子类对beanFactory做后置处理
-
invokeBeanFactoryPostProcessors: 激活各种BeanFactory处理器,对所有的 BeanDefinitionRegistryPostProcessors 、手动注册的 BeanFactoryPostProcessor 以及通过配置文件方式的 BeanFactoryPostProcessor 按照 PriorityOrdered 、 Ordered、no ordered 三种方式分开处理、调用, 这里委托给PostProcessorRegistrationDelegate实现,其中BeanDefinitionRegistryPostProcessor此处解析了一些注入的类Configuration,Import注解
-
registerBeanPostProcessors: 注册 BeanPostProcessor,排序加入beanFactory
-
initMessageSource: 初始化上下文中的资源文件,如国际化文件的处理等
-
initApplicationEventMulticaster: 初始化上下文事件广播器
-
onRefresh: 给上下文子类可扩展初始化其他特殊Bean, spring mvc中的bean是在此处定义,tomcat用线程池创建bean
-
registerListeners: 在所有bean中查找listener bean,然后注册到广播器中
-
finishBeanFactoryInitialization: 初始化剩下的单例Bean(非延迟加载的)
-
finishRefresh: 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
-
destroyBeans: 销毁beans
-
cancelRefresh: 重置容器激活标签