【SpringBoot启动流程】00 - SpringApplication构造方法

544 阅读4分钟

1. 启动 - 构造方法

先来看看,在SpringBoot中做了什么,来启动我们的应用:

一般我们如此启动应用:

 @SpringBootApplication
 public class DemoApplication {
 ​
     public static void main(String[] args) {
         //normal starter
         SpringApplication.run(DemoApplication.class, args);
     }

在实际工程中,在启动类头上我们会加上需要的组件,例如:

  • @EnableFeign

...

等等,这些涉及到自动注入功能,后面再说。

上面的代码实际上到这一步:

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

run方法的注释如下:

 Run the Spring application, creating and refreshing a new
 {@link ApplicationContext}.
 @param args the application arguments (usually passed from a Java main method)
 @return a running {@link ApplicationContext}

因此,这里我们先来看看,在构造方法中我们做了什么。

1.1 构造方法 - 启航

构造方法中,我们最终到达这一步:

 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();
         this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
         setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         this.mainApplicationClass = deduceMainApplicationClass();
     }

这里我们传下来的ResourceLoader是个null,primarySources是我们的启动类。

  • 前面几行没啥东西,就是看到的那个样子;
  • WebApplicationType.deduceFromClasspath()返回的一般都是SERVLET,这个就看我们的相关配置了。

接下来看这个方法:

getBootstrapRegistryInitializersFromSpringFactories

1.1.1 - getBootstrapRegistryInitializersFromSpringFactories

这个方法全文如下:

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

这里我们会遇到第一个Spring中很重要的术语:factory

 getSpringFactoriesInstances(Bootstrapper.class)

不过在看这个之前,我们先看看在构造方法中,我们是如何获取这些实例的。

1.1.1.1 getSpringFactoriesInstances

核心的方法为代码块中,获取factoryNames名字和创建工厂实例这两个。

1.我们先看获取名字的
 SpringFactoriesLoader.loadFactoryNames
 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
       classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
 }
 ​
 //
 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
       return result;
    }
 ​
    result = new HashMap<>();
    try {
        //只有这里是关键的知识
       Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
       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();
             String[] factoryImplementationNames =
                   StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
             for (String factoryImplementationName : factoryImplementationNames) {
                result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                      .add(factoryImplementationName.trim());
             }
          }
       }
 ​
       // Replace all lists with unmodifiable lists containing unique elements
       result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
             .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
       cache.put(classLoader, result);
    }
    catch (IOException ex) {
       throw new IllegalArgumentException("Unable to load factories from location [" +
             FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
 }

这里的FACTORIES_RESOURCE_LOCATION,实际上是SpringFramework下的META-INF/spring.factories;也就是说:

  • 初始化的时候,我们会从这里读取配置文件中指定的。

随后会做一系列的缓存归类等操作,这里我们就先按下不表。

2.获取到名字之后,接下来我们创建工厂类。
 private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
       ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> 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;
 }

什么嘛,这里就是用反射来构造对象,没有什么玄乎的东西。

3 回到getSpringFactoriesInstances

这里就没什么可以说的了,现在我们可以对这个方法下个定义:

  • 读取配置文件中的factories,这里可能是缓存里读,也可能是从文件里读。
  • 如果拿到了,就进行装配。

回到主流程

获取完我们需要的数据之后,就继续进入主流程了。

我们将设置的都放到initializers中:

 .forEach(initializers::add);
 initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

很遗憾的地方是,在这一步我们并没有获取实际的到instance。如果这里出现了值,可能是版本不一致,也有可能是对配置文件做出了修改。

1.1.2 回到主流程

接下来我们剩三行代码了:

 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
 this.mainApplicationClass = deduceMainApplicationClass();

上面两行,这次不同的是获取到值了,这里就是跟启动流程中需要的,因此就有数据了。

而最后一行的目的和实现也很简单,就是从当前运行栈中获取启动类的类名。

1.1.3 小结

目前我们分析的是:启动Spring中,

SpringApplication

对象的构造方法。

这里我们从配置文件META-INF/spring.factories中读取了

  • ApplicationContextInitializer,设置成Initializer初始化器
  • ApplicationListener,设置成监听器

并根据参数以及其他的东西,配置了启动类和类名相关的关键参数。

单凭一个构造方法,可能不太能够构建起对于整个Spring的印象,不过在这里有几个key-point,对于源码脉络有一定的了解:

  • 目前我们清楚了,Spring中也内置了spring.factories配置文件,作为初始化时候的一部分配置的配置源。

  • 在这里我们根据名称:

    • factory
    • listener

    已经了解到,在构造方法中,Spring已涉及到两个设计模式:

    • 工厂方法

    • 监听者方法