0%

SpringBoot 启动流程解析

Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

SpringBoot 启动过程

在建立 SpringBoot 项目时,一般会提供一个 ProjectApplication 类用来启动服务。因此可以将整个过程切分为两步:创建一个 SpringApplication 的启动器、执行它的 run 方法。

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

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

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        prepareContext(bootstrapContext, 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, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

我们可以先不关心 SpringApplication 做了什么,先看一下 SpringBoot 的生命周期。

SpringBoot 生命周期

我们可以重点关注一下 SpringApplicationRunListeners 类。它将 Spring Application 的生命周期划分为以下几个过程:

  • starting(spring.boot.application.starting)
  • environment prepared(spring.boot.application.environment-prepared)
  • context prepared(spring.boot.application.context-prepared)
  • context loaded(spring.boot.application.context-loaded)
  • started(spring.boot.application.started)
  • running(spring.boot.application.running)
  • failed(spring.boot.application.failed)

对于 Spring Application 的启动,总过程可以被分为两阶段:第一阶段是通过 BootstrapContext 将 Application Context 所需要的扩展环境和扩展组件都加载好;第二阶段是运行 Application Context。

因此还可以将以上生命周期做更为详细的切分。如下:

  • BootstrapContext 启动器加载必要的环境和组件
    • starting(spring.boot.application.starting)
    • environment prepared(spring.boot.application.environment-prepared)
    • context prepared(spring.boot.application.context-prepared)
  • Application Context 应用运行提供服务
    • context loaded(spring.boot.application.context-loaded)
    • started(spring.boot.application.started)
    • running(spring.boot.application.running)

首先,我们需要了解一下 SpringApplicationRunListeners 的作用。以 starting 为例,说明其功能:

void starting(ConfigurableBootstrapContext bootstrapContext, 
              Class<?> mainApplicationClass) {
    // 当 APP 到达 starting 状态时(其他状态也是相同的操作),调取 doWithListeners 函数
    doWithListeners(
        "spring.boot.application.starting", 
        (listener) -> listener.starting(bootstrapContext),
        (step) -> {
            if (mainApplicationClass != null) {
                step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
        }
    );
}

// 该方法的生命周期控制(即前处理、执行后处理、结束前处理)由 StartupStep 触发,分为四步:
// 1. 触发 starting 生命周期 listener 调用前事件
// 2. 调用注册的 SpringApplicationRunListener 监听器
// 3. 触发 starting 生命周期 listener 调用后事件
// 4. 触发 starting 生命周期 listener 调用结束事件
private void doWithListeners(String stepName, 
                             Consumer<SpringApplicationRunListener> listenerAction, 
                             Consumer<StartupStep> stepAction) {
    StartupStep step = this.applicationStartup.start(stepName);
    this.listeners.forEach(listenerAction);
    if (stepAction != null) {
        stepAction.accept(step);
    }
    step.end();
}

对于启动失败的情况,由于涉及到异常处理等问题,因此与其他生命周期处理方式不同,但是也大同小异。

void failed(ConfigurableApplicationContext context, Throwable exception) {
    doWithListeners("spring.boot.application.failed",
                    (listener) -> callFailedListener(listener, context, exception), (step) -> {
                        step.tag("exception", exception.getClass().toString());
                        step.tag("message", exception.getMessage());
                    });
}

private void callFailedListener(SpringApplicationRunListener listener, 
                                ConfigurableApplicationContext context,
                                Throwable exception) {
    try {
        listener.failed(context, exception);
    }
    catch (Throwable ex) {
        if (exception == null) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        //... ignore some code
    }
}

starting 阶段

// 刚刚讲过的 SpringApplicationRunListeners,为 SpringBoot 启动提供生命周期的 Listener 机制。
SpringApplicationRunListeners listeners = getRunListeners(args);
// 创建准备 ApplicationContext 运行环境的 BootstrapContext
// 在 starting 和 environment prepared 生命周期,会被当作入参传入 listener。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();

listeners.starting(bootstrapContext, this.mainApplicationClass);

environment prepare 阶段

//ApplicationArguments 是 SpringApplication.run(ProjectApplication.class, args) 中对入参 args 的封装
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根据 WebApplicationType 加载对应的环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);

listeners.environmentPrepared(bootstrapContext, environment);

context prepare 阶段

DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
      "Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
   environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
         deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
configureIgnoreBeanInfo(environment);

//创建 Context
//根据 APP 的类型会创建如下 Application Context
//SERVLET -> AnnotationConfigServletWebServerApplicationContext
//REACTIVE -> AnnotationConfigReactiveWebServerApplicationContext
//NONE -> AnnotationConfigApplicationContext
context = createApplicationContext();
//设置生命周期回调
context.setApplicationStartup(this.applicationStartup);
//设置环境变量
context.setEnvironment(environment);
//后处理
postProcessApplicationContext(context);

//调用注册的 ApplicationContextInitializer Listener
applyInitializers(context);

listeners.contextPrepared(context);
bootstrapContext.close(context); 

context load 阶段

// 把 args 参数当作 Bean 注入到容器中
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (this.lazyInitialization) {
   context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 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);

start 阶段

// 调用 ApplicationContext 中的 refresh 重新加载应用
refreshContext(context);
// 模板方法,供重载实现
afterRefresh(context, applicationArguments);

listeners.started(context);

running 阶段

// 调用 ApplicationRunner 和 CommandLineRunner 接口的实现类
callRunners(context, applicationArguments);
try {
   listeners.running(context);
}
catch (Throwable ex) {
   handleRunFailure(context, ex, null);
   throw new IllegalStateException(ex);
}

SpringBoot 环境变量加载

spring.factories 加载

对于 /META-INF/spring.factories 文件的配置读取,在创建 SpringApplication 类的时候就通过 SpringFactoriesLoader 读取完成了。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   //.....
  
   //不知道干什么的
   this.resourceLoader = resourceLoader;
   
   //从 spring.factories 文件中加载以下四个注解的实现
   //1. BootstrapRegistryInitializer
   //2. ApplicationContextInitializer
   //3. ApplicationListener
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
}

@SuppressWarnings("deprecation")
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
   ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
   getSpringFactoriesInstances(Bootstrapper.class).stream()
         .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
         .forEach(initializers::add);
   initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   return initializers;
}

但这些加载的配置文件本质上并不能在 Environment 中找到,spring.factories 其作用的定位就是用 KV 的形式记录所有需要加入容器的类。这涉及到了 SpringBoot 的自动配置的原理,会在以后章节详细介绍。

Web Application 参数源加载

由于涉及到了 Web Container 的加载,因此与使用的 Web Container 类型强相关。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
  // Create and configure the environment
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  //...
}

因此可以选择如下三种默认的 Web Container Environment。

private ConfigurableEnvironment getOrCreateEnvironment() {
  if (this.environment != null) {
    return this.environment;
  }
  switch (this.webApplicationType) {
    case SERVLET:
      return new ApplicationServletEnvironment();
    case REACTIVE:
      return new ApplicationReactiveWebEnvironment();
    default:
      return new ApplicationEnvironment();
  }
}

拿 ApplicationServletEnvironment 举例,由于是一个继承初始化过程,我们将继承链上的内容整合在一起进行一个较为宏观的概括介绍。

//AbstractEnvironment
this.propertySources = propertySources;
this.propertyResolver = createPropertyResolver(propertySources);

//获取多个 PropertySource
customizePropertySources(propertySources);
protected void customizePropertySources(MutablePropertySources propertySources) {
  // Servlet config init parameters property source
  propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
  // Servlet context init parameters property source
  propertySources.addLast(new StubPropertySource("servletContextInitParams"));
  // JNDI property source
  if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
    propertySources.addLast(new JndiPropertySource("jndiProperties"));
  }
  
  propertySources.addLast(
    // JVM system properties property source, Invoke System.getProperties()
    new PropertiesPropertySource("systemProperties", getSystemProperties()));
  propertySources.addLast(
    // System environment property source, Invoke System.getenv()
    new SystemEnvironmentPropertySource("systemEnvironment", getSystemEnvironment()));
}

Command Line 参数加载

对于 Spring Application 运行时入参,SpringBoot 将其封装到 ApplicationArguments 的默认实现类中。其后会在 environment prepare 阶段注入环境变量,在 context prepare 阶段当作 Bean 注入 Container。

//ApplicationArguments 是 SpringApplication.run(ProjectApplication.class, args) 中对入参 args 的封装
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

environment prepare 阶段注入环境变量

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
  //...
  configureEnvironment(environment, applicationArguments.getSourceArgs());
}

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
  if (this.addConversionService) {
    environment.setConversionService(new ApplicationConversionService());
  }
  configurePropertySources(environment, args);
  configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
  MutablePropertySources sources = environment.getPropertySources();
  if (!CollectionUtils.isEmpty(this.defaultProperties)) {
    DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
  }
  // 可以看到 CommandLine Args通过 SimpleCommandLinePropertySource 解析到 Environment
  if (this.addCommandLineProperties && args.length > 0) {
    String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
    if (sources.contains(name)) {
      PropertySource<?> source = sources.get(name);
      CompositePropertySource composite = new CompositePropertySource(name);
      composite.addPropertySource(
        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
      composite.addPropertySource(source);
      sources.replace(name, composite);
    }
    else {
      sources.addFirst(new SimpleCommandLinePropertySource(args));
    }
  }
}

context prepare 阶段当作 Bean 注入 Container

// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
  beanFactory.registerSingleton("springBootBanner", printedBanner);
}

SpringBoot Context 加载

从 SpringBoot 的生命周期可知,环境变量是在 context prepare 阶段加载的。

Application Context 加载

ConfigurableApplicationContext context = createApplicationContext();
// 注入 APP 生命周期事件处理器
context.setApplicationStartup(this.applicationStartup);
// 注入 Environment 环境配置
context.setEnvironment(environment);
// 注入 ResourceLoader、ClassLoader、ConversionService 组件
postProcessApplicationContext(context);
applyInitializers(context);

listeners.contextPrepared(context);
bootstrapContext.close(context);

我们主要关注一下 Application Context 到底加载了哪些组件?

private ApplicationContextFactory applicationContextFactory = (webApplicationType) -> {
  switch (webApplicationType) {
    case SERVLET:
      return new AnnotationConfigServletWebServerApplicationContext();
    case REACTIVE:
      return new AnnotationConfigReactiveWebServerApplicationContext();
    default:
      return new AnnotationConfigApplicationContext();
  }
};

protected ConfigurableApplicationContext createApplicationContext() {
   return this.applicationContextFactory.create(this.webApplicationType);
}

可以看到根据不同的应用类型,加载了不同的 Application Context。其涉及到的 Application Context 层次与职责,会在以后章节详细介绍。

Bootstrap Context 加载

DefaultBootstrapContext 是 ConfigurableBootstrapContext 的默认实现类,而 ConfigurableBootstrapContext 整合了 BootstrapContext 和 BootstrapRegistry 两个接口。BootstrapContext 可以在 starting、environment prepared 和 context prepared 期间使用,Can be used to register instances that may be expensive to create, or need to be shared before the ApplicationContext is available。而 BootstrapRegistry 提供了对 instances 的注册功能以及对 BootstrapContext 关闭时的回调方法。

DefaultBootstrapContext

BootstrapContext 在 SpringBoot 的生命周期中一直被 SpringApplicationRunListeners 当作入参传入其 starting 和 environmentPrepared 方法,方便在 ApplicationContext 未 prepared 之前共享内容。

SpringBoot BeanDefinition 加载

从 SpringBoot 的生命周期可知,在 context load 阶段调用的 load 方法创建了一个 BeanDefinitionLoader 用来注册 ProjectApplication 的 BeanDefinition。我们可以看到,SpringBoot 将 BeanDefinition 的加载完全委托给 BeanDefinitionLoader 进行处理。

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   this.sources = sources;
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

由 BeanDefinitionLoader 的定义来说,可以看到其可以解析 Annotation、XML、Groovy 等形式的 BeanDefinition。

protected void load(ApplicationContext context, Object[] sources) {
   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();
}

我们可以看到,可以有四种方式加载我们的 PrimarySource:Class、Resource、Package、CharSequence。

void load() {
   for (Object source : this.sources) {
      load(source);
   }
}

private void load(Object source) {
   if (source instanceof Class<?>) {
      load((Class<?>) source);
      return;
   }
   if (source instanceof Resource) {
      load((Resource) source);
      return;
   }
   if (source instanceof Package) {
      load((Package) source);
      return;
   }
   if (source instanceof CharSequence) {
      load((CharSequence) source);
      return;
   }
   throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

以通过 Class 方式加载为例,在逻辑判断之后,委托 AnnotatedBeanDefinitionReader 注册 Bean。

private void load(Class<?> source) {
   if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
      // Any GroovyLoaders added in beans{} DSL can contribute beans here
      GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
      ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
   }
   if (isEligible(source)) {
      this.annotatedReader.register(source);
   }
}

由于涉及到注册 Bean 相关内容,关于注册器的相关层级及职责在后面章节会介绍。

SpringBoot 运行过程

从 SpringBoot 的生命周期可知,在 start 阶段调用了 ApplicationContext 的 refresh 方法,可以说是同 BeanDefinition 的加载一样,直接委托给 ApplicationContext 执行。而且预留了 afterRefresh 模板方法等待子类重载实现。

// 调用 ApplicationContext 中的 refresh 重新加载应用
refreshContext(context);
// 模板方法,供重载实现
afterRefresh(context, applicationArguments);

listeners.started(context);

refreshContext 定义

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }
    refresh(context);
}

protected void refresh(ConfigurableApplicationContext applicationContext) {
    applicationContext.refresh();
}

afterRefresh 定义

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

这部分涉及到了 ApplicationContext 的原理,会在以后章节详细介绍。