likes
comments
collection
share

SpringBoot 源码系列 0:启动流程

作者站长头像
站长
· 阅读数 21

前言

本文是笔者在阅读 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() 之前进行自定义

通过以上代码注释以及方法说明,我们大概能明白这段构造函数的作用。

同时简单了解一下这段方法中的几个概念。

  1. web 应用类型:
    • SERVLET:使用传统的 Servlet 容器(Tomcat,Jetty)来部署或允许应用程序
    • REACTIVE: 使用响应式编程模型容器(如 Netty)来部署或应用程序
    • NONE:不提供 web 应用 当然,我们也可以通过配置文件来显示指定类型,如下:
spring:
    main: 
        web-application-type = xxx
  1. BootstrapRegistryInitializer
    • 该接口通常用于应用程序启动时执行一些预处理或者注册的任务,如果我们没有显示使用的情况下,通常 spring 并没有使用。
  2. 监听器(碍于篇幅则不详细解答)

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 下只有五个参数,如图:

SpringBoot 源码系列 0:启动流程

这些文件大概包含一下内容:

  • 当前操作系统的环境变量
  • jvm 的一些配置信息
  • -D 方式所配置的 JVM 环境

此时还是没有加载我们自定义的 yml/properties 文件的。

 listeners.environmentPrepared(bootstrapContext, environment);

而经过下一步发布监听器事件之后,我们才会多出了新的属性文件。

SpringBoot 源码系列 0:启动流程

根据名称我们也能明白这便是我们自己写的 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
评论
请登录