SpringBoot 源码系列 0:启动流程
前言
本文是笔者在阅读 Spring 源码以及相关书籍(如《Spring源码深度解析》)博客的抄书笔记,由于个人实力有限,部分解析可能会出现错误,如有误解请指正。
SpringApplication 的初始化
首先来看 Spring 容器的启动入口。
public static void main(String[] args) {
SpringApplication.run(UserCenterApplication.class, args);
}
SpringApplication 构造函数
逐渐步入,则会来到 SpringApplication 的构造函数。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 保存启动类信息
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 初始化 Web 应用类型以及环境, 默认为 Servlet
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载 引导注册中心
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载 初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 获取 监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 定位 main 方法
this.mainApplicationClass = deduceMainApplicationClass();
}
创建一个新的 SpringApplication 实例,应用程序上下文将从指定的主要来源加载 Bean,该实例可以在调用 run() 之前进行自定义
通过以上代码注释以及方法说明,我们大概能明白这段构造函数的作用。
同时简单了解一下这段方法中的几个概念。
- web 应用类型:
- SERVLET:使用传统的 Servlet 容器(Tomcat,Jetty)来部署或允许应用程序
- REACTIVE: 使用响应式编程模型容器(如 Netty)来部署或应用程序
- NONE:不提供 web 应用 当然,我们也可以通过配置文件来显示指定类型,如下:
spring:
main:
web-application-type = xxx
- BootstrapRegistryInitializer
- 该接口通常用于应用程序启动时执行一些预处理或者注册的任务,如果我们没有显示使用的情况下,通常 spring 并没有使用。
- 监听器(碍于篇幅则不详细解答)
run 方法
当构造函数执行完毕,接下来浏览 Run 方法
public ConfigurableApplicationContext run(String... args) {
// 1.记录开始时间
long startTime = System.nanoTime();
// 2.创建一个默认启动上下文,用于程序的启动过程中
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 3. 设置 “java.awt.headless” 模式属性,默认为 true,进行简单的图形处理
configureHeadlessProperty();
// 4. 获取 Spring 的监听器类,用于在启动过程中通知每个监听器相应的事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 5. 通知监听器上下文即将启动,传递相关信息。
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 6. 初始化默认应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 7. 解析应用程序参数,用于配置应用程序的参数
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 8. 配置对 BeanInfo 的忽略
configureIgnoreBeanInfo(environment);
// 9. 打印 Banner
Banner printedBanner = printBanner(environment);
// 10.会利用ApplicationContextFactory.DEFAULT,根据应用类型创建对应的Spring容器。
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 11. 准备刷新上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 12. 刷新上下文
refreshContext(context);
// 13. 刷新后的处理操作,默认情况下没什么处理的
afterRefresh(context, applicationArguments);
// 14. 停止计时
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
// 15. 输出日志记录
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 15. 发布应用上下文监控事件
listeners.started(context, timeTakenToStartup);
// 16. 执行所有的 Runner 允许器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
运行 Spring 容器,创建并刷新新的 ApplicationContext
2. createBootstrapContext()
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
return bootstrapContext;
}
仔细观察该方法,我们能明白发现一个熟悉的名词(bootstrapRegistryInitializers)。
哪怕没有之前的简单功能介绍,我们也能明白只是将注册的上下文容器作为参数用来执行 initalize 方法在返回。
因此,没有之前的简介,我们也大概能明白他的作用了。
4. getRunListeners()
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
当代码步入到该方法时,其实我们也可以很清晰的看出来,就说从 spring.factories 中查询数据。
而通关观察参数,则能明白默认key为 org.springframework.boot.SpringApplicationRunListener, 获取到的类型则为 EventPublishingRunListener 。
需要注意的是该方法返回的是一个 Collection 集合,也就是说明如果我们指定了或者导入其他写有监听器的一来是,会出现多个监听器的情况。
但在默认情况下,仅有一个 EventPublishingRunListener。
而可能有人会对 applicationStartup 感到一丢丢的陌生,现在简单的来介绍一下他的概念。
我们都知道,在飞机飞行的过程中,黑匣子会记录飞机的各项指标,以便于出事后的问题排查;那么相对于的,JFR 也就是 jvm 的黑匣子。
更加详细可以查看 hashcon 大佬的博客说明(zhuanlan.zhihu.com/p/122247741…
7. prepareEnvironment()
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建或获取 环境类,由于在开始环境时我们默认 web 为 servlet,因此返回类为 StandardServletEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境(基于命令行参数等来源)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 附加配置属性源(如 application,properties/yml文件、系统属性等)
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
// 调整默认属性源文件,将用户自定义的配置优先于默认配置考虑
DefaultPropertiesPropertySource.moveToEnd(environment);
// 环境前缀检查
Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
// 绑定到 spring 应用
bindToSpringApplication(environment);
// 检查环境,如果不是自定义环境则转换
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
// 重新加载属性源,以保证获取到的属性都是最新的
ConfigurationPropertySources.attach(environment);
return environment;
}
7.1 properties/yml 等加载
ConfigurationPropertySources.attach(environment);
如果我们仔细观察 debug 栏,此时的环境变量下的 propertySources 下只有五个参数,如图:
这些文件大概包含一下内容:
- 当前操作系统的环境变量
- jvm 的一些配置信息
- -D 方式所配置的 JVM 环境
此时还是没有加载我们自定义的 yml/properties 文件的。
listeners.environmentPrepared(bootstrapContext, environment);
而经过下一步发布监听器事件之后,我们才会多出了新的属性文件。
根据名称我们也能明白这便是我们自己写的 yml文件。
那么很简单,我们便能明白 spring 是利用监听器事件来实现文件的。
而这具体的类便是:
- ConfigFileApplicationListener (2.4 版本以前)
- ConfigDataEnvironmentPostProcessor (2.4 版本以后)
碍于篇章,我们以后在详细介绍。
10.createApplicationContext()
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
用于创建ApplicationContext 的策略方法。默认情况下,该方法将尊重任何显式设置的应用程序上下文类或工厂,然后再返回到合适的默认值
其实在步入子类,我们便会明白这段代码是根据默认的 web 应用类型来生成对于的 context。
而通过前文的多次验证,我们也能明白默认值为 servlet。因此这里会创建 AnnotationConfigServletWebServerApplicationContext。
需要注意的是:
AnnotationConfigServletWebServerApplicationContext 构造函数中会创建 AnnotatedBeanDefinitionReader。
而在 AnnotatedBeanDefinitionReader 构造函数中会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);,
该方法将一些必要Bean(如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor 等)注入到了容器中**。
11. prepareContext
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置环境
context.setEnvironment(environment);
// 应用上下文后处理
postProcessApplicationContext(context);
// 调用所有注册的 applicationInitalizer 进行预处理
applyInitializers(context);
// 触发所有 SpringApplicationRunListener 监听器的 ContextPrepared 事件方法。添加所有的事件监听器
listeners.contextPrepared(context);
// 关闭引导上下文 (避免资源重复)
bootstrapContext.close(context);
// 日志记录
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 注册两个单例 bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory){
// 是否允许循环引用
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
//是否允许 bean 覆盖
((DefaultListableBeanFactory)
beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
// 是否支持延迟初始化
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 属性源排序处理器
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources
// 加载的是启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 这里将启动类加入到 beanDefinitionMap 中,为后续的自动化配置做好了基础
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
load()
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// 从上下文中获取 BeanDefinitionRegistry并依次创建出 BeanDefinitionLoader 。这里将sources作为参数保存到了 loader 中。也就是 loader 中保存了启动类的Class信息
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
将 Bean 加载到应用上下文中
其实该方法只是将 应用上下文的 beandefinition 保存到 loader 中。 真正的关键是 loader.load() 方法中。
而关于 bean 的加载,我们后续再聊。
12. refreshContext()
该方法只是将容器进行刷新,避免中途修改了代码或者环境变量,以保证获取最新的数据。
此节我们也之后在进行细讲。
参考资料
《Spring源码深度解析》 Spring | Home
转载自:https://juejin.cn/post/7382151765381906451