前言

本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。

本篇主要是针对Spring Boot项目的启动流程进行分析。因为文章的定位是笔者自己学习,所以作者打算对Spring Boot的源码尽量做到逐行分析,因此可能会比较繁杂一些。

PS: 说真的就算是逐行分析也不是每行都能看懂,这里的看懂是理解它为什么要要这么写,而不是看不懂这行代码实现了什么。就像都知道1+1=2,但是不是每个人都能证明1+1=2一样。但是不要灰心,经验告诉我就算看的时候不懂,但是机缘巧合下一定会明白并且还有可能学习它的写法。感叹一句: 人外有人,天外有天,保持谦卑,必有所成。

在公司的时候看的Spring Boot的版本是2.7.x。在家里看的Spring Boot的版本是2.6.x。发现不同版本的Spring Boot在启动的时候还是有一定的差异的。

接下来将进入Spring Boot 2.6.9的源码分析。

项目启动

都知道启动一个Spring Boot项目需要写一个类,在类的main方法中调用Spring Application的run()方法。

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

进入该方法发现又调用了SpringApplication内部的一个重构的run方法,这个run方法的参数是一个Class数组,根据方法参数名称就能猜出来这是给后面用来定位一些资源信息的,其实就是根据我们提供的类信息去加载bean。

Spring Boot源码分析一:启动流程

从这里可以看出我们自定义的启动类的main方法中调用SpringApplication的run方法的时候不一定要传递自己。我们可以传递任意一个类,主要是根据我们要加载的资源情况来决定就好了。这里常常有人提到的是如果我们的配置类所在的包不在我们的启动类下就加载不到了,那我们给run方法传递参数的时候换一个其他的类是不是就可以了呢。道理就是这么个道理,但是我们实际运用的时候还是遵循常规吧。

run方法

这是我们应用调用的SpringApplication类静态的run方法。

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

这个静态的run方法中调用了重载的静态的run方法。

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

从这个静态的run方法中可以看出,首先实例化了一个SpringApplication,然后调用了实例的run方法。

那么我们在我们的启动类中是不是也可以直接创建SpringApplication的实例,然后调用实例的run方法呢,这样的话我们就可以设置多个资源类咯。(Class<?>[] primarySources)

其实这里自己实例化SpringApplication是对Spring Boot一些高级场景的用法(个人理解),因为往后跟的时候会发现会用到很多SpringApplication的一些属性,我们可以通过先实例化SpringApplication的对象然后对其的属性进行赋值,最后再调用run方法。

这里需要注意的是不要因为业务代码写的太多了就小看框架中的构造方法,在框架中实例化一个对象的时候,往往有很多重要的属性或者逻辑都是在构造方法中实现的。

看到这里我个人总结出一点学习的地方,那就是对一个框架产品的入口的设计和编码。

SpringApplication实例化

又来了相同的写法又出现在了构造方法上。 PS:像这些都是笔者随心有感而发,大神切勿见怪,见笑见笑~~~

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}

重载的构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   // 这个资源加载器默认是null
   this.resourceLoader = resourceLoader;
   // 主资源列表进行非空判断,这里可以在自己项目中学习这种写法,也是一种减少if else的方式
   Assert.notNull(primarySources, "PrimarySources must not be null");
   // 将资源类保存到全局变量primarySources中,这里用了LinkedHashSet,这个类型的集合即可以保证有序又可以去重,即同时拥有List和Set的特效。
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 判断应用是SERVLET类型还是REACTIVE类型,我们都知道现在Spring有两种模式了就是servlet和reactive具体判断方法的实现见下面的代码片段一。当然这里会返回SERVLET
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // 读取类路径下配置文件META-INF/spring.factories中配置的BootstrapRegistryInitializer
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   // 读取类路径下配置文件META-INF/spring.factories中配置的ApplicationContextInitializer
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   / 读取类路径下配置文件META-INF/spring.factories中配置的ApplicationListener
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances()这个方法有点厉害哟,实现方式可以借鉴到自己的项目中哟,而且个人觉得这个方法也是开启Spring Boot神话的第一步。Spring Boot的SPI。

代码片段一

通过判断类路径下是否有相应的类。这里我们的Spring项目中也可以用这个工具类哟ClassUtils

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

OK 今天先到这里吧。

See you next time :)