七、浅析SpringBoot启动流程(源码分析)

208 阅读7分钟

项目中我们通过SpringApplication.run启动SpringBoot应用,但却不知内部的逻辑,与Spring的整合,因此本文将从源码出发,对于SpringApplication的启动流程做简要阐述。

一般使用springboot项目,都是这样启动的,SpringApplication.run(xxx),本文将会分两部分处理:SpringApplication配置阶段,称为SpringBoot初始化。run方法逻辑,称为SpringBoot启动。

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

1. SpringBoot初始化

内部实际的逻辑是SpringApplication然后调用run方法。

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

这提供了启发,如果需要对SpringApplication进行配置的话,可以通过new SpringApplication的方式,进行设置。比如关闭Banner打印可以这样启动。

SpringApplication springApplication = new SpringApplication(MyApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);

同时Spring也提供了Builder模式,方便通过budiler进行构建SpringApplication,例如:

new SpringApplicationBuilder(MyApplication.class)
      .bannerMode(Banner.Mode.OFF)
      .run(args);

这里对SpringApplication的配置,会在SpringApplication运行的时候,运用起来。现在先看下new SpringApplication内部初始化了哪些内容。


public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   //解析web类型
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   //设置初始化器
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //设置监听器 
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //判断mian函数所在的类
   this.mainApplicationClass = deduceMainApplicationClass();
}

web类型,主要通过判断对应的class是否存在,默认有三种类型,REACTIVE、SERVLET和NONE。比如SERVLET就会判断javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext是否存在(通过类加载器尝试加载,通过是否会报错判断)。

//WebApplicationType#deduceFromClasspath
static WebApplicationType deduceFromClasspath() {
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}

获取初始化器和监听器逻辑类似,都是从spring.factories文件中获取对应的类,然后通过反射进行实例化,最后在进行排序(如根据Ordered接口)。把获取到的集合,设置到SpringApplication的属性中

注意,此时实例的类还没有加入到Spring IOC容器。

//SpringApplication#getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//SpringApplication#getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   //SpringFactoriesLoader.loadFactoryNames内部会从spring.factories文件中进行加载对应类型的类。
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   //进行实例化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   //进行排序,如设置了@Order之类的注解,或者实现了Ordered接口的情况
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

而判断main函数所在的类,是通过运行时的堆栈信息中,查看main方法所在的类,然后通过反射进行获取。

//SpringApplication#deduceMainApplicationClass
private Class<?> deduceMainApplicationClass() {
   try {
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
         if ("main".equals(stackTraceElement.getMethodName())) {
            return Class.forName(stackTraceElement.getClassName());
         }
      }
   }
   catch (ClassNotFoundException ex) {
      // Swallow and continue
   }
   return null;
}

2. SpringBoot启动流程

去掉SpringBoot启动无关的代码后,启动逻辑如下所示:获取获取SpringApplicationRunListener、Environment准备、Banner打印、准备SpringBoot上下文、启动Spring上下文、ApplicationRunner执行等逻辑,会根据启动的阶段,将启动流程分为三部分:

  1. 启动前准备阶段
  2. 启动阶段
  3. 启动后阶段
//SpringApplication#run
public ConfigurableApplicationContext run(String... args) {
   //获取SpringApplicationRunListener
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      //environment准备
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //Baner打印
      Banner printedBanner = printBanner(environment);
      //创建ApplicationContext
      context = createApplicationContext();
      //获取SpringBootExceptionReporter对象
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //准备上下文
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //启动Spring上下文
      refreshContext(context);
      //启动后逻辑
      afterRefresh(context, applicationArguments);
      //日志打印
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      //ApplicationRunner执行
      callRunners(context, applicationArguments);
   }catch (Throwable ex) {
     //异常处理
     handleRunFailure(context, ex, exceptionReporters, listeners);
   }
   //ApplicationListener执行
   listeners.running(context);
   return context;
}

2.1 准备阶段

2.1.1 获取SpringApplicationRunListener

底层的实现逻辑,是从spring.factories文件中获取SpringApplicationRunListener的对象,然后封装成SpringApplicationRunListeners,SpringApplicationRunListeners的逻辑其实就是内部有多个SpringApplicationRunListener,然后执行SpringApplicationRunListeners方法的时候,内部实际上就是循环调用SpringApplicationRunListener的方法,这种处理方法,在Spring中比较场景的就是xxxComposite,比如:WebMvcConfigurerComposite内部实际上包含WebMvcConfigurer的集合。

private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger,
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

在Spring内置的SpringApplicationRunListener,只有EventPublishingRunListener。EventPublishingRunListener会在整个Spring启动阶段针对不同事件进行发送,比如:ApplicationStartingEvent、ApplicationReadyEvent等事件,内部实际上依赖的是Spring提供的SimpleApplicationEventMulticaster类。

Spring 事件可以参考本人的另外一篇文章:浅析Spring事件

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

在EventPublishingRunListener内,事件的处理会分为两步分,Spring ApplicationContext创建前、创建后。

  • 在Spring ApplicationContext创建前,会通过自己实例化的SimpleApplicationEventMulticaster进行事件发布,此时Spring IOC容器还没有启动。所以ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationContextInitializedEvent和ApplicationPreparedEvent事件的监听,需要手动通过SpringApplication进行添加,或者通过spring.factories文件进行添加。
  • 在Spring ApplicationContext创建后,会通过Spring IOC容器进行事件的发布。此类的事件有:ApplicationStartedEvent、ApplicationReadyEvent。此类事件监听器,可以直接通过Spring Bean进行处理。

这个前后的节点,就在SpringApplicationRunListener的contextLoaded方法中,会把SpringApplication中的监听器都加入到ApplicaitonContext中,然后再发布事件,此后的事件就会通过ApplicaitonContext进行发布了。

//EventPublishingRunListener#contextLoaded
public void contextLoaded(ConfigurableApplicationContext context) {
   for (ApplicationListener<?> listener : this.application.getListeners()) {
      if (listener instanceof ApplicationContextAware) {
         ((ApplicationContextAware) listener).setApplicationContext(context);
      }
      context.addApplicationListener(listener);
   }
   this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

2.1.2 准备Environment

Environment不打算深入,里面的内容也比较多,这里我们需要知道会准备Environment即可。内部的逻辑会先创建ConfigurableEnvironment,getOrCreateEnvironment内部的逻辑会根据web类型进行判断:

  • 如果是SERVLET,则创建StandardServletEnvironment
  • 如果是REACTIVE,则创建StandardReactiveWebEnvironment
  • 其他,则创建StandardEnvironment

创建完后会进行配置,同时也会用SpringApplicationRunListener调用environmentPrepared方法。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   //创建ConfigurableEnvironment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   //配置ConfigurableEnvironment
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   //SpringApplicationRunListener处理
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

2.1.3 Banner打印

这是个无关紧要的功能,所以简单介绍一下,内部的逻辑会找图片Banner,或者文本Banner

  • 图片Banner,可以通过spring.banner.image.location指定位置;还会从ResourceLoader中分别尝试获取banner.gif、banner.jpg、banner.png是否有图片;
  • 文本Banner,可以通过spring.banner.location指定位置;如果没有配置的话,会从ResourceLoader中尝试加载banner.txt。

如果以上都找不到,并且SpringApplication没有设置Banner的情况下,会用Spring内置的Banner,也就是启动会打印SpringBoot的文本。

2.1.4 创建ApplicationContext

会根据web类型创建ApplicationContext

  • Servlet:AnnotationConfigServletWebServerApplicationContext
  • Reactive:AnnotationConfigReactiveWebServerApplicationContext
  • 其他:AnnotationConfigApplicationContext
//SpringApplicaiotn#createApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
   }
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

2.1.5 准备ApplicationContext

会对ApplicationContext设置Environment,然后会处理一下ApplicationContext内部逻辑就是设置了一下属性,如注册BeanNameGenerator到Spring IOC容器,设置resourceLoader之类。接着会调用ApplicationContextInitializer的initialize接口。调用SpringApplicationRunListener的contextPrepared方法;最后会进行日志打印,如:The following profiles are active的日志。

//SpringApplication#prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   //设置ApplicationContext
   postProcessApplicationContext(context);
   //调用ApplicationContextInitializer的initialize
   applyInitializers(context);
   //调用SpringApplicationRunListener的contextPrepared
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   //...
}

接着会在注册ApplicationArguments和Banner到Spring IOC容器中,会设置allowBeanDefinitionOverriding值到IOC容器。如果是延迟初始化,还会增加LazyInitializationBeanFactoryPostProcessor到ApplicationContext中。

//SpringApplication#prepareContext
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
   beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
   ((DefaultListableBeanFactory) beanFactory)
         .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
   context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

最后会加载Source,然后调用SpringApplicationRunListener的contextLoaded方法

//SpringApplication#prepareContext
// 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);

2.2 启动

默认情况下,会调用ApplicationContext注册一个ShutdownHook,紧接着就是调用applicationContext的refresh方法,该方法会启动Spring的IOC容器。

ApplicationContext的启动流程,可以查看本人的另外一篇文章:浅析Spring启动流程

//SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
   refresh(context);
}
//SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}

2.3 启动后

启动会调用afterRefresh,这是一个空实现,是个模版方法,子类可以根据需要进行实现。紧接着会进行日志打印,类似Started xxx in xxx seconds的日志。然后会调用SpringApplicationRunListener的started方法;然后会调用callRunners方法。

callRunners内部的逻辑就是获取实现ApplicationRunner、CommandLineRunner接口的Bean,然后分别调用。这两个的接口类似,只是run方法的参数不同。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   List<Object> runners = new ArrayList<>();
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   AnnotationAwareOrderComparator.sort(runners);
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

最后会调用SpringApplicationRunListener的running方法,根据前面的描述,实际上就是会发送ApplicationReadyEvent的事件。

通过以上分析可以得知,如果我们某个Bean的逻辑要在SpringBoot启动完在处理,可以有以下几种方法处理:

  • 实现ApplicationRunner接口
  • 实现CommandLineRunner接口
  • 监听ApplicationReadyEvent事件。

3. 参考资料

  • Spring boot源码2.2.x分支
  • 《SpringBoot编程思想(核心篇)》