【超详细的Spring源码分析 —— 01 Spring IoC 在宏观上的执行流程 】

476 阅读5分钟

当今,SpringBoot、SpringCloud 盛行,而无论怎么发展,背后的根基依然是 Spring 的 IoC 与 Aop,这两个核心是无论如何也不会变化的。

为了加深自己对 Spring 的理解,笔者打算针对 Spring 源码分析,内容涉及到了 IoC、Aop、事务管理,在分析的同时写下自己的见解与心得,也是把我的一点微薄知识分享给大家。

首先,我们要知道 IoC 它要做的几件重要的事情:

  1. 配置元数据资源
  2. 创建工厂
  3. 解析元数据获取到 beanDefinition,并装配到工厂
  4. 通过 BeanDefinition 实例化 bean,然后获取到这个 bean

以上是一个宏观上的步骤,Spring 其实把复杂的逻辑都封装到了内部,对外提供了简单的接口供我们调用。

好了废话不多说,我们先对 "IoC 主要使用了哪些组件"、"IoC 大致的执行流程" 进行分析。

一、元数据定义

我们先对最常见的一种元数据配置方式 "XML" 进行分析,首先构建一个简单的 POJO 类:

public class Student {
    private String name;
    private int age;
}
<bean id="student" class="com.whl.spring.beans.Student">
    <property name="name" value="whl" />
    <property name="age" value="18" />
</bean>

二、IoC 执行流程的宏观分析

我们知道 Spring 会把创建好的 Bean 放在 IoC 工厂,但从哪里解析 Bean 、如何解析 Bean、如何把解析后的 Bean 放入 IoC 工厂 ······ 这些问题都值得我们思考。

下面这段代码,但凡是学习过 Spring 的各位应该都不会陌生,ApplicationContext 这个上下文对象即是一个 IoC 工厂,并在 BeanFactory 基础之上增加了许多特性。

public class SpringClient {
    public static void main(String[] args) {
      	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

在简单的 new ClassPathXmlApplicationContext("applicationContext.xml") 背后,Spring 背后还封装了资源抽象、注册器初始化、读取器初始化、以及通过读取器解析资源并将 Bean 组装到注册器的工作,详细内容可以参考 refresh() 方法,关键的流程用代码来表述如下所示:

public class SpringClient {
    public static void main(String[] args) {
      	// 1. 首先是创建资源
        // spring 将不同类型的 Resource 进行了抽象, 针对不同类型的资源都给出了不同的实现:
        // 比如最常见的 classpath 下的资源
        Resource resource = new ClassPathResource("applicationContext.xml");

        // 又比如文件系统中的资源
        // Resource resource = new FileSystemResource("~/.../applicationContext.xml");
        
        // ...
        // 还有诸多针对不同类型资源的实现
      
        // 2. 创建工厂(也可以理解为创建 BeanDefinition 注册器)
        // 创建一个默认的、可列举的Bean工厂, 这个工厂需要引入元数据资源才能创建bean
      	// 这个工厂实现了 BeanDefinitionRegistry 接口, 也可以认为它是一个注册器
      	DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
			
      	// 3. 创建 BeanDefinition 读取器
      	// 这个 BeanDefinitionReader 也是一个接口, 分别对不同的资源类型, 比如 properties、groovy、xml 进行了不同的实现
      	// 由于我们的 Resource 是基于xml的, 因此创建一个支持xml的读取器
        // 这个读取器在构造时需要引入一个 BeanDefinitionRegistry 类型的实例
        BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);

        // 4. 通过读取器读取资源, 完成 beanDefinition 的装配
      	// 它会根据 resource 中定义的元数据生成 beanDefinition, 然后组装到 defaultListableBeanFactory 中
      	// 当 beanDefinition 装配完成后, 读取器的使命也就结束了
        beanDefinitionReader.loadBeanDefinitions(resource);

        // 5. 然后我们通过工厂来获取到相应的示例
        // 这个过程就涉及到了 bean 的实例化、单例缓存池、依赖注入等内容
        Student student = defaultListableBeanFactory.getBean("student", Student.class);
    }
}

上述代码的逻辑都比较简单,逻辑层次也比较清晰,但有一个关键点我必须要强调:

ApplicationContext 被初始化的同时,工厂内部的 Singleton Bean 也都会被实例化。这一切的秘密都在于 ApplicationContext 在初始化时调用的 refresh() 方法,这个方法包含了解析、创建 Bean 的相关逻辑。

有兴趣的可以提前去 debug 了解一下相关的流程。

但无论是哪种类型的 ApplicationContext,它们的底层都是相同的,都会通过一个方法入口来真正执行实例化 Bean 的逻辑。这个方法就是 BeanFactory 这个接口提供的 getBean() 方法。

如果你调试过 ApplicationContext 的初始化流程,那么你一定能够发现,在执行到 getBean() 之前,工厂类中的单例缓存池里是不存在任何一个 Bean 实例的。

上述代码也是一样的,在执行到 defaultListableBeanFactory.getBean("student", Student.class); 之前,工厂中一定不会存在任何一个单例 Bean 实例。

三、总结

现在我们应该清楚,IoC 背后的流程大致能够划分为如下几个步骤,分别是:

  1. 创建资源抽象 Resource
  2. 创建注册器,用于注册 BeanDefinition
  3. 创建读取器,用于读取并解析资源
  4. 通过读取器解析资源,组装 BeanDefinition 到注册器
  5. 通过 BeanDefinition 实例化出 bean 对象,供我们使用

Spring 无论是以哪种方式创建 Bean(可以是注解,也可以是配置文件,也可以是其他任何一种方式),底层都离不开上述的步骤,就算有些许差别,但也都八九不离十了。简单来说,以上这部分内容就是整个 IoC 最核心、最精华的部分了。

下一篇文章,我将会针对上述的 1~3 步骤进行详细分析,看看 IoC 容器在解析并装配 BeanDefinition 之前都需要完成哪些准备工作。