Spring Boot 启动流程简析

865 阅读5分钟

一、如何启动

@SpringBootApplication
public class StarterApplication {
    public static void main(String[] args) {
        SpringApplication.run(StarterApplication.class, args);
    }
}

最基本的Spring Boot应用只需要一个注解@SpringBootApplication、一行代码SpringApplication.run(class, args)即可启动。

其中,SpringApplication.run(class, args)的两个参数:

  1. class,启动类
  2. args,启动应用时的命令行参数

二、启动流程

进入SpringApplication.run方法,该方法很简单,只有一行代码:

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

主要做了两件事情:

  1. 构造SpringApplication对象
  2. 执行run方法,运行程序

1、构造SpringApplication

基本流程:

image.png

核心代码:

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类型:非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();
}

默认情况下,事件监听器主要有以下几个:

image.png

除了事件监听器以外,还有两个比较重要的组件:

  1. BootstrapRegistryInitializer,用于向启动上下文(即BootstrapContext,也有些资料称其为引导上下文)注册InstanceSupplier,一个用于创建实例对象的工厂方法。但是要注意的是该工厂方法仅支持“单例模式”,每个类型的工厂方法只能注册一个;
  2. ApplicationContextInitializer,初始化应用上下文的时候,执行一些自定义的初始化逻辑,此处的应用上下文即指Spring容器环境;

2、执行run方法

基本流程:

image.png

核心代码:

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    // 创建启动上下文,该上下文对象,仅在spring boot启动阶段会使用到。在启动后会关闭,仅留应用上下文对象存在,即下面的ConfigurableApplicationContext对象
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    // 配置awt配置,headless模式
    configureHeadlessProperty();
    // 获取生命周期监听器,通过该对象,执行生命周期事件回调
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass); // 生命周期-starting
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 环境变量相关扩展,但是此处仅仅只是读取了相关的扩展点信息,并没有将其加载到应用上下文中
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 准备banner信息
        Banner printedBanner = printBanner(environment);
        // 仅创建上下文,但是内部并没有创建bean
        context = createApplicationContext();
        // 标志启动阶段
        context.setApplicationStartup(this.applicationStartup);
        // 实际上此处才将读取的环境变量的信息进行了装载
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // kp spring核心流程,刷新上下文,创建bean
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        listeners.started(context, timeTakenToStartup); // 生命周期回调
        // 触发ApplicationRunner与CommandLineRunner
        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);
    }
    // 返回context对象。
    // 所以获取context对象除了ApplicationContextAware接口以外,也可以通过启动方法的返回值获取
    return context;
}

其中有两个方法需要再详细看下,一是加载环境变量、配置的prepareEnvironment方法,一是配置应用上下文的prepareContext方法。

3、prepareEnvironment

最核心的是支持通过EnvironmentPostProcessor,加载自定义的变量配置

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // kp 此处会发布ApplicationEnvironmentPreparedEvent事件,通知到EnvironmentPostProcessorApplicationListener对象。
    //  该对象负责处理注册的EnvironmentPostProcessor,该扩展点支持用于自定义环境变量等配置
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
            "Environment prefix cannot be set via properties.");
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

4、prepareContext

需要特别注意的是,在prepareEnvironment方法中加载到的变量、配置信息,实际上到执行prepareContext的时候,才加载到应用上下文中。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment); // 装载环境变量信息
    postProcessApplicationContext(context); // 对上下文对象在refresh之前进行任意的后置处理,,扩展点。可以通过自定义SpringApplication实现扩展
    applyInitializers(context); // 上下文初始化器,自定义一些初始化操作
    listeners.contextPrepared(context);
    bootstrapContext.close(context); // 关闭启动上下文 -- 两个上下文:启动上下文,,应用上下文
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
        // spring 两个重要参数
        // 1. 循环引用;2. bean覆盖
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    // 懒加载配置
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // 支持PropertySource通过order注解指定顺序
    context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
    // 加载bean
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

核心操作:

  1. 执行应用上下文初始化器,即在构建SpringApplication对象时,注册的ApplicationContextInitializer
  2. 关闭启动上下文BootstrapContext
  3. 加载变量配置信息
  4. 应用上下文部分参数配置

三、获取上下文

Spring提供了ApplicationContextAware接口,通过实现该接口,可以获取到应用上下文对象。除了该方式还有别的方法吗? 仔细观察SpringApplication.run方法,实际上它是有返回值的:

image.png

这个对象就是应用上下文:

@SpringBootApplication
@Slf4j
public class StarterApplication implements ApplicationContextAware {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(StarterApplication.class, args);
        // 此处获取的context对象,是应用上下文对象,与通过ApplicationContextAware获取到的上下文对象是同一个        
        log.info("method return context: {}", context);
    }

    @Override    
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("ApplicationContextAware context: {}", applicationContext);
    }
}

执行结果:

image.png

从日志中可以看出是同一个对象。