本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

SpringBoot 启动流程

撰写本文时,源码参考上游最新版的 RELEASE 版 2.3.4.RELEASE, 那就直接从官方例子启动类开始挖啦。

1) SpringBoot 启动源码

@SpringBootApplication
public class Application {

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

@SpringBootApplication 注解我们暂时不提,下文中会重新讲到这个注解, SpringApplication.run 即 SpringBoot 启动的入口:

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

我们沿着 run 方法向下走, 第一个 run 方法中用数组包装了一下启动类 Class, 第二个 run 方法中一共干了两件事儿, 不妨拆开来看:

  1. new SpringApplication(primarySources)
    创建 SpringApplication 实例的过程即 SpringBoot 初始化的过程, 在本文 1.1 会讲到
  2. .run(args)
    run 方法即 SpringBoot 真正的启动过程, 在本文 1.2 中讲到

1.1) SpringApplication

我们来看一下 new SpringApplication(primarySources) 的源码:

public class SpringApplication {
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
}

清理掉非重要代码后, 大概做了以下几件事儿:

  1. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    初始化 this.primarySource 主要资源类集合, 放入启动类, 暂且不提稍微眼熟一下
  2. this.webApplicationType = WebApplicationType.deduceFromClasspath();
    初始化 this.webApplicationType, 判断我们的启动程序类型, 参考 1.1.1
  3. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    初始化 this.initializers 初始化器集合, 从 spring.factories 获取, 参考 1.1.2
  4. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    初始化 this.listeners 监听器集合, 从 spring.factories 获取, 参考 1.1.3
  5. this.mainApplicationClass = deduceMainApplicationClass();
    初始化 this.mainApplicationClass 主应用 Class, 通过获取堆栈信息, 取调用链中最近的一个主函数类, 参考 1.1.4

1.1.1 SpringApplication.webApplicationType

初始化 this.webApplicationType, 判断我们的启动程序类型

this.webApplicationType = WebApplicationType.deduceFromClasspath();

public enum WebApplicationType {
    NONE,
    SERVLET,
    REACTIVE;
    private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };
    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

    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;
    }
}

这段源码很简单:

  1. 如程序依赖 SpringWebflux 但不依赖 SpringMVC 和 JERSEY, 即返回 REACTIVE 响应式程序
  2. 如程序不依赖 Servlet, 即返回 NONE 非 WEB 程序
  3. 否则即返回 SERVLET 程序

一般我们的项目都为 SERVLET 程序

1.1.2) SpringApplication.initializers

初始化 this.initializers 初始化器集合, 从 spring.factories 获取

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

先来看 getSpringFactoriesInstances 方法:

public class SpringApplication {
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
}
  1. ClassLoader classLoader = getClassLoader();
    获取类加载器, 不太重要这里不提
  2. Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    传入 ApplicationContextInitializer.class, 从 spring.factories 获取初始化器集合, 具体流程如下

    public final class SpringFactoriesLoader {
        public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
            String factoryTypeName = factoryType.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
        }
    }
    • 核心方法为 loadSpringFactories 返回 Map 类型集合, 然后从中获取 key 为 ApplicationContextInitializer.class 的初始化器类全路径集合

      public final class SpringFactoriesLoader {
      public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
      
      private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
          MultiValueMap result = cache.get(classLoader);
          if (result != null) {
              return result;
          }
      
          try {
              Enumeration urls = (classLoader != null ?
                      classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
              result = new LinkedMultiValueMap<>();
              while (urls.hasMoreElements()) {
                  URL url = urls.nextElement();
                  UrlResource resource = new UrlResource(url);
                  Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                  for (Map.Entry entry : properties.entrySet()) {
                      String factoryTypeName = ((String) entry.getKey()).trim();
                      for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                          result.add(factoryTypeName, factoryImplementationName.trim());
                      }
                  }
              }
              cache.put(classLoader, result);
              return result;
          }
          catch (IOException ex) {
              throw new IllegalArgumentException("Unable to load factories from location [" +
                      FACTORIES_RESOURCE_LOCATION + "]", ex);
          }
      }
      }
    • 代码看着多, 但做的事儿很少, 大致如下:
      1. 尝试从缓存直接拿
      2. 拿到包含所有 spring.factories 路径的 urls
      3. 遍历每个 spring.factories 文件, 从中获取 key 为 ApplicationContextInitializer.class 的数据如下
        org.springframework.context.ApplicationContextInitializer=\
        org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
        org.springframework.boot.context.ContextIdApplicationContextInitializer,\
        org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
        org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
        org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
      4. 将 value split 一下与 key 一起放入 Map 集合中
      5. 缓存至内存让后续不再重复产生磁盘 IO
      6. 返回从 spring.factories 中取得的初始化器集合
  3. List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    创建初始化器实例, 上一步只是将初始化器类的全路径取得, 我们还需要反射创建一下实例:

    public final class SpringFactoriesLoader {
        private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
                ClassLoader classLoader, Object[] args, Set names) {
            List instances = new ArrayList<>(names.size());
            for (String name : names) {
                try {
                    Class instanceClass = ClassUtils.forName(name, classLoader);
                    Assert.isAssignable(type, instanceClass);
                    Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                    T instance = (T) BeanUtils.instantiateClass(constructor, args);
                    instances.add(instance);
                }
                catch (Throwable ex) {
                    throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
                }
            }
            return instances;
        }
    }

    源码也很简单, 只是反射一下创建实例, 放进集合返回

  4. AnnotationAwareOrderComparator.sort(instances);
    顾名思义, 对集合排序, 若初始化器上有 @Order 注解则优先按照 order 数值排序, 从小到大, 这里不细讲了意义不大

最后调用 setInitializers 赋值到 initializers 中, 这里不贴代码了

初始化器部分就讲完啦, 总结:

  1. 从 spring.factories 中获取初始化器列表
  2. 创建初始化器实例
  3. 对初始化器通过 @Order 注解排序
  4. 赋值到 SpringApplication.initializers

1.1.3) SpringApplication.listeners

初始化 this.listeners 监听器集合, 从 spring.factories 获取
监听器的初始化和初始化器几乎一样, 可参考上一节 1.1.2

  1. 从 spring.factories 中获取初始化器列表
  2. 创建监听器实例
  3. 对监听器通过 @Order 注解排序
  4. 赋值到 SpringApplication.listeners

1.1.4) SpringApplication.mainApplicationClass

this.mainApplicationClass = deduceMainApplicationClass();

初始化 this.mainApplicationClass 主应用 Class, 通过获取堆栈信息, 取调用链中最近的一个主函数类
源码是一个很经典的获取方法调用链的例子:

public class SpringApplication {
    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;
    }
}

看代码应该能看懂, 看不懂走一下 debug 就明白啦这里不讲了

1.2) SpringApplication.run

我们来康一哈源码:

public class SpringApplication {
    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            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;
    }
}

先来简单了解一下每一行代码的作用:

  1. StopWatch stopWatch = new StopWatch(); stopWatch.start();
    开头的 StopWatch 相关的代码, 作用主要是记录程序启动的时间, 然后在程序基本启动完成后输出一下启动程序的耗时, 具体参考 21,23 行, 太简单这里不细讲
  2. ConfigurableApplicationContext context = null;
    定义了一下 Spring 的 ApplicationContext, 未赋值
  3. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    初始化异常报告器集合, 用于存放从 spring.factories 中初始化的的异常报告器, 下面还会提到
  4. configureHeadlessProperty();
    设置系统参数 java.awt.headless 用于模拟各种服务器缺少的输入输出设备, 这里也不细讲, 可以百度 java headless
  5. SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();
    从 spring.factories 初始化 SpringApplicationRunListeners, 它相当于 SpringBoot 监听器模型中, 广播器的一个包装工具类, 这里 start 了所有支持 ApplicationStartingEvent 事件的监听器, 参见 1.2.1
  6. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    将命令行传参包装了一下, 方便取参

1.2.1) listeners.starting

先来看看 getRunListeners 这个方法:

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

还是 getSpringFactoriesInstances 这个方法, 这里不重复提了, 从 spring.factories 读取 key 为 SpringApplicationRunListeners 的对象做初始化
, 在 spring-boot.jarspring-boot-autoconfigure.jar 中的 spring.factories 中能找到的类只有: org.springframework.boot.context.event.EventPublishingRunListener

class SpringApplicationRunListeners {

    private final Log log;

    private final List<SpringApplicationRunListener> listeners;

    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }
}

初始化后丢进成员变量, 啥事儿也没做, 那我们接着看看 listener.starting(); 吧, 这个才是本节的核心方法:

class SpringApplicationRunListeners {
    void starting() {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.starting();
        }
    }
}

上文嗦到 listeners 中只有一个 EventPublishingRunListener, 它是监听器模型中, 广播器的一个包装工具类, 可以优雅的广播相应的事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    @Override
    public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }
}

如这个 starting() 方法, 广播了一个 ApplicationStartingEvent 事件给监听器们, 我们直接康康这个广播方法:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    @Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
        return ResolvableType.forInstance(event);
    }
}
  1. 第一个 multicastEvent 方法中, 在广播事件前, 调用了 resolveDefaultEventType 方法对事件 Pack 了一下, 这里不详细嗦了
  2. 获取一下任务执行器
  3. getApplicationListeners 获取对事件感兴趣的监听器, 参见 1.2.1.1
  4. 如果任务执行器不为空, 则交给任务执行器线程运行监听器, 反之则在主线程运行
  5. 顺着 invoke 的方法往里走可以发现最后只是调用了监听器的 onApplicationEvent 方法 listener.onApplicationEvent(event);
1.2.1.1) getApplicationListeners

这是 SpringBoot 监听器相关的挺重要的一个方法, 获取对事件感兴趣的监听器集合, 先来康康源码:

public abstract class AbstractApplicationEventMulticaster
        implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {

        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

        // Quick check for existing entry on ConcurrentHashMap...
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }

        if (this.beanClassLoader == null ||
                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            // Fully synchronized building and caching of a ListenerRetriever
            synchronized (this.retrievalMutex) {
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }
}
  1. 根据传入事件, 生成对应的 CacheKey 缓存 key
  2. 尝试用 CacheKey 缓存中拿 retriever 缓存的监听器列表, 如果之前有缓存则直接返回
  3. 校验一下事件, 然后进入同步代码块, 仔细康康可以看出这里用了双重校验锁, 单例里常见的那个
    1. 再尝试从缓存中取, 以免重复 Put
    2. 使用 retrieveApplicationListeners 函数获取对传入事件感兴趣的监听器列表, 代码太长就不贴了, 简单说一下实现
      1. 遍历 SpringBoot 初始化过程加载的所有监听器, 大致可分为两类监听器, SmartApplicationListenerApplicationListener
      2. SmartApplicationListener 则调用其 supportsEventType 方法判断是否支持此事件, ApplicationListener 则通过获取泛型来判断是否支持该事件
      3. 将支持该事件的监听器集合返回
    3. Put 进缓存中