月夜星空-liulq

--愿美好接踵而至

写在前面

   本系列文章为源码讲解,阅读时一定要打开你手上的SpringBoot源码,配合阅读!

    这篇文章开始,我们正式进入源码的学习,如果只看文章可能会有一些摸不到头脑,所以希望大家学习的过程中,配合你手上的源码一起来看,另外大家手上的SpringBoot版本和我的可能不一样,所以源码上会略有不同,但整体思路都是一样的。

    读源码的时候,大家不要每一个方法都跟进去,那样会给自己带来很多困扰,所以我们只详细阅读重要的方法,不重要的方法我们一语带过就好,我在和大家一起学习源码的过程中,也会指出哪些是重要的方法,哪些可以一语带过,方便大家的阅读。

前言

    从启动源码上来看,SpringBoot主要就分为两步,第一步是完成了SpringBootApplication的实例化,第二步就是执行它的run方法,我们今天主要学习第一步,实例化的过程,看看这个实例化都做了哪些工作。

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

SpringApplication的实例化

    我们进入SpringBoot的构造函数里面,为了避免过分的啰嗦,这里就不逐行的讲解了,主要看一些重要的代码即可:

    
    //primarySources 是启动类
    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");

//primarySources 就是我们的启动类,我的启动类是SpringBootTest,然后把他放到SpringApplication这个类里面的全局变量primarySources中
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));

//指定应用程序类型,进入方法里面会发现有三种reactive、none、servlet,默认都是servlet
this.webApplicationType = this.deduceWebApplicationType();

//下面两行是设置初始化器和监听器 这两个方法非常重要,下面会详细讲解
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

//找到我们的主函数
this.mainApplicationClass = this.deduceMainApplicationClass(); }

    接下来我们来分析下这里面的工作:

    this.setInitializers和this.setListeners两个方法分别是设置初始化器和监听器,执行了这两个方法之后,我们SpringApplication类中的全局变量中就包含了预加载的一些初始化器和监听器了,那么是怎么加载进来的呢,加载进来的初始化器和监听器包括什么呢?这两个方法都是通过this.getSpringFactoriesInstances方法来找到预加载的信息的,我们进入这个方法看看:

    为了方便描述,我把下面的代码片段做个编号。

代码片段1:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//创建类加载器 ClassLoader classLoader
= Thread.currentThread().getContextClassLoader();

//这行执行之后,我们就可以找到预加载的类的名称,type是我们传进来的class,就是上面那两行重要代码中的ApplicationContextInitializer和AppliListener,
//loadFactoryNames是加载的具体方法,将在下面的代码片段1.1中讲解 Set
<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

// List
<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }

代码片段1.1

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
//读取到传进来的class名,然后加载类信息 String factoryClassName
= factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }

    loadSpringFactories这个方法不进入方法内部查看了,加载的过程不是我们的重点。经过了loadSpringFactories之后,可以从spring-boot和spring-boot-autoconfigure两个包的META-INF/spring.factories文件中加载到所要的初始化器和监听器的类名。

    代码片段1.1 执行之后,我们看看代码片段1 中的Set<String>names的值:

 

 

     我们已经从上面提到的配置文件中找到了需要的初始化器、监听器的全类名,然后执行下面一行createSpringFactoriesInstances -- 实例化。这个createSpringFactoriesInstances 方法我们先不进入查看,因为后面在讲解类的实例化(也就是我在《SpringBoot源码系列(一)》的图一中,画的步骤3的那里)的时候会更加详细的来了解这个方法,只需要知道,完成了这里的操作之后,我们的初始化器监听器类被实例化完成了。

     上面已经说,通过getSpringFactoriesInstances 可以获取到配置文件中的初始化器和监听器,然后看看this.setInitializers和this.setListeners两个方法的内部,里面是一样的,我们只拿其中一个来看看:

    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList();
        this.listeners.addAll(listeners);
    }

    就是把我们获取到的初始化器和监听器缓存到类的全局变量中。

    到这里为止,SpringApplication的实例化做的工作就完成了,我们回忆一下这里都做了什么,简单的来说,就是向SpringApplication的全局变量中加载一些后续使用的信息,包括项目的启动类、项目启动类型servlet、初始化器、监听器等。

 

posted on 2022-01-16 17:09  月夜星空-liulq  阅读(342)  评论(0)    收藏  举报