SpringBoot启动分析

177 阅读7分钟

SpringBoot启动分析

SpringBoot版本:2.0.6.RELEASE

本文粗浅地分析SpringBoot启动流程,了解SpringBoot和Spring容器的一些扩展点。

入口

从我们指定的启动类中的main方法开始,run方法传入了主类的Class对象。

public static void main(String[] args)  {
    ApplicationContext context = SpringApplication.run(Main.class, args);
}

追踪run方法可以看到,首先new了一个SpringApplication对象,接着调用了它的run方法。

public static ConfigurableApplicationContext run(Class<?> primarySource,
                                                 String... args) {
    return run(new Class<?>[] { primarySource }, args); // 将主类的Class对象放在只有一个元素的数组里传给下面的run方法
}

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

SpringApplication实例化阶段

SpringApplication的描述是这样的:

本类从Java main方法引导和启动一个Spring应用。默认情况下引导步骤如下:

  1. 创建ApplicationContext实例;
  2. 注册CommandLinePropertySource将命令行参数暴露为Spring属性;
  3. 刷新应用上下文,加载所有单例bean;
  4. 触发所有CommandLineRunnerbean。

实例化SpringApplication的构造方法。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader; // null
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 数组转为有序集合LinkedHashSet
    // 推断webApplication类型:NONE/SERVLET/REACTIVE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 初始化spring.factories中的ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 初始化spring.factories中的ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推断引导类
    this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances方法在META-INF/spring.factories文件中分别查找ApplicationContextInitializer以及ApplicationListener,获取类的全路径名,然后通过反射逐个加载类并实例化。

ApplicationContextInitializer和ApplicationListener的用途:

在SpringBoot应用中,classpath上会包含许多jar包,有些jar包需要在ConfigurableApplicationContext#refresh()调用之前对应用上下文做一些初始化动作,因此它们会提供自己的ApplicationContextInitializer实现类。

ApplicatonContext事件机制是观察者模式。每当ApplicatonContext发布ApplicationEvent事件时,ApplicationListener将会被触发。例如,ConfigFileApplicationListener就是利用这个机制来在环境准备好之后加载配置文件的。

SpringApplication运行阶段

SpringApplication#run()是SpringBoot启动运行的核心方法。

public ConfigurableApplicationContext run(String... args) {
    // 秒表,用于对代码进行计时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 初始化所有SpringApplicationRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 创建ApplicationArguments对象,处理args
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
        // 准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                                 applicationArguments);
        // 设置系统属性spring.beaninfo.ignore
        configureIgnoreBeanInfo(environment);
        // 打印banner
        Banner printedBanner = printBanner(environment);
        // 实例化应用上下文对象
        context = createApplicationContext();
        // 初始化所有SpringBootExceptionReporter,用来报告启动过程中的错误
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
        // 准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);
        // 刷新上下文
        refreshContext(context);
        // 刷新完上下文后调用,目前是个空方法
        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;
}

SpringApplicationRunListeners

run方法中各个阶段伴随着listeners的调用,这里首先介绍listeners——SpringApplicatonRunListenersSpringApplicatonRunListenersSpringApplicatonRunListener的集合。

SpringApplicationRunListener也是在spring.factories中定义的。它的作用是,在SpringBoot启动的过程中可以通过SpringApplicationRunListener接口回调来让用户在启动的各个流程中可以加入自己的逻辑。

接口定义如下:

public interface SpringApplicationRunListener {
    // run方法启动后调用
	void starting();
    // Environment构建完成时调用
	void environmentPrepared(ConfigurableEnvironment environment);
    // ApplicationContext构建完成时调用
	void contextPrepared(ConfigurableApplicationContext context);
    // ApplicationContext完成加载,但没有刷新之前调用
	void contextLoaded(ConfigurableApplicationContext context);
    // ApplicationContext完成刷新并启动后,callRunners执行之前调用
	void started(ConfigurableApplicationContext context);
    // run()方法执行的最后一步调用
	void running(ConfigurableApplicationContext context);
    // 运行出错时调用
	void failed(ConfigurableApplicationContext context, Throwable exception);
}

SpringApplicationRunListener与前面的ApplicationListener区别是,SpringApplicationRunListener的生命周期是在run方法中,每次运行run都会重新创建实例;ApplicationListenerSpringApplication构造好之后不会再改变。

另外ApplicationListener监听的事件其实是靠EventPublishingRunListener触发的。推送的SpringApplicationEvent及对应的RunListener方法如下:

  • ApplicationStartingEvent - starting
  • ApplicationEnvironmentPreparedEvent - eviromentPrepared
  • ApplicationPreparedEvent - contextLoaded
  • ApplicationStartedEvent - started
  • ApplicationReadyEvent - running
  • ApplicationFailedEvent - failed

contextLoaded方法没有事件推送。

ApplicationArguments

ApplicationArguments接口用来处理main方法的参数args。SpringBoot提供了默认实现DefaultApplicationArguments

ApplicationArguments的主要方法就两个。

/**
 * 获取option参数值列表,即以--开头的参数
 * 例如:
 *   1. --foo 返回空列表
 *   2. --foo=bar 返回["bar"]
 *   3. --foo=bar --foo=baz 返回["bar", "baz"]
 *   option不存在时返回null。
 */
List<String> getOptionValues(String name);
/**
 * 获取non-option参数列表
 */
List<String> getNonOptionArgs();

准备环境

这个阶段创建并配置了Environment。

Environment即应用程序的运行环境,由两部分组成:profiles和properties。profiles就是spring.profiles.active的值;properties指键值对。

private ConfigurableEnvironment prepareEnvironment(
    SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
    // 根据webApplicationType创建对应的ConfigurableEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置configureEnvironment
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    // 将configureEnvironment绑定到SpringApplication对象
    bindToSpringApplication(environment);
    // 若ConfigurableEnvironment非自定义的,则转换其类型
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
            .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

ConfigurableEnvironment的继承关系如下。

prepareEnvironment方法返回了ConfigurableEnvironment接口的实例,根据webApplicationType返回不同的子类实例:

  • 非web应用NONE - StandardEnvironment
  • Servlet应用SERVLET - StandardServletEnvironment
  • Reactive应用REACTIVE - StandardReactiveWebEnvironment

配置configureEnvironment分为方面,一是configurePropertySources,处理应用中所有PropertySource;二是configureProfiles,为应用配置激活的profiles。

创建和准备上下文

根据web应用类型创建应用上下文ConfigurableApplicationContext实例,然后准备上下文。

准备上下文主要的一步是执行一系列的初始化器ApplicationContextInitializer。

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    // 向上下文设置前面准备好的environment
    context.setEnvironment(environment);
    // 上下文后置处理
    postProcessApplicationContext(context);
    // 执行之前收集的ApplicationContextInitializer
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // Add boot specific singleton beans
    // 注册单例bean:applicationArguments
    context.getBeanFactory().registerSingleton("springApplicationArguments",
                                               applicationArguments);
    if (printedBanner != null) {
        // 注册单例bean:printedBanner
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // Load the sources
    // 加载启动方法源码
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

刷新上下文

跟踪refreshContext()可以发现最终调用了AbstractApplicationContext#refresh()方法。

刷新上下文过程中初始化了所有bean。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) { // 加锁
        // Prepare this context for refreshing.
        // 为应用上下文刷新做准备:设置时间、记录刷新日志、初始化属性源中的占位符、验证必要的属性
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 获取bean factory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 初始化bean factory
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 注册所有BeanFactoryPostProcessor
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 调用BeanFactoryPostProcessor各个实现类的方法
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册BeanPostProcessor
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 初始化MessageSource
            initMessageSource();

            // Initialize event multicaster for this context.
            // 初始化ApplicationEventMulticaster
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 子容器特殊的刷新逻辑,例如创建WebServer
            onRefresh();

            // Check for listener beans and register them.
            // 注册实现了ApplicationListener接口的bean
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 实例化所有非懒加载单例
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 完成上下文刷新,处理LifeCycle
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

BeanFactoryPostProcessor和BeanPostProcessor

BeanFactoryPostProcessorBeanPostProcessor都是Spring初始化bean时对外暴露的扩展点。

BeanFactoryPostProcessor的定义如下。

/*
 * beanFactory的后置处理器
 */
@FunctionalInterface
public interface BeanFactoryPostProcessor {
    
    // 在bean定义加载之后、实例化之前执行,可以在这里获取和修改bean定义(BeanDefinition)
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanPostProcessor的定义如下。

/**
 * bean的后置处理器
 */
public interface BeanPostProcessor {

    // bean初始化操作(例如afterPropertiesSet或自定义的init方法)之前执行
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

    // bean初始化之后的后置处理器
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

ApplicationListener

前面提到,在SpringApplication实例化阶段,注册了spring.factories中所有的ApplicationListner@Configuration注解的ApplicationListener当时并没有被注册,它们是在刷新上下文阶段注册的。

因此实现了@Configuration注解的ApplicationListener无法监听到注册之前推送的事件,例如:ApplicationStartingEventApplicationEnvironmentPreparedEventApplicationPreparedEvent

LifeCycle

执行finishRefresh时,会由LifecycleProcessor注册所有Lifecycle并按序执行其start方法。

public interface Lifecycle {
	void start();
	void stop();
	boolean isRunning();
}

有时候我们需要在Spring初始化所有bean之后,接着执行一些启动需要的异步服务,就可以使用Lifecycle

一般使用其子接口SmartLifecycle,phase越小优先级越高。

总结

SpringBoot启动主要步骤概括如下:

  1. 初始化SpringApplication实例
  2. 创建并准备环境
  3. 创建应用上下文
  4. 准备应用上下文
  5. 刷新上下文