前言

之前一直没有系统学习过Spring,这次从 Spring 的核心功能入手,首先学习其 IoC 容器的实现。

之前也不是没有看过官网文档和源码,但是最大的问题就是容易在庞大的体系结构以及非线性的代码中迷失,因为高手的架构很多并不聚集在一起,而是散落在各个地方。

所以这次主要使用以下学习资料多方学习印证参考,争取把Spring的核心特性拿下,并从其设计中学到更多的东西。

参考资料:

  • tiny-spring 虽然是 7 年前的老项目了,但是非常的经典,用最少的代码实现了 Spring 的核心 IoC 功能,拿来自己跟着写并进行学习非常的合适,强烈推荐。
  • 极客时间 —— 小马哥Spring核心课程 , 这个课程是收费的,用了两百多讲去讲 IoC 相关的东西,深度有,还会扩展不少知识,信息量很大,就是需要知识基础和耐心进行学习
  • 芋艿 —— 精进Spring 系列,从源码到机制,分析、学习 Spring。 知识星球关注的一个 up 主,写了不少深入源码层面的学习资料,可操作性强,也可以作为参考。不过同样这些资料也是付费资料。

下面就以 tiny-spring 为线索,学习 Spring 的核心 IoC 容器 的实现。


tiny-spring

作者使用 git tag 将整个 IoC 的实现分为了多个步骤,查看相对应的代码只需要根据 git 命令切换到对应的分支即可,非常方便,同时也非常适合进行学习。

git-tag相关知识:

使用 git-tag 命令就可以看到该git仓库中的所有tag分支了:

下面根据这几个步骤,来看看 Spring 是怎样实现 IoC 容器的。

1.step1-最基本的容器

切换到step1:

git checkout step-1-container-register-and-get

前言:

这一步的类很少,但是是最核心的关键点。

IoC 最基本的角色有两个:

  • 容器
  • Bean

  • BeanDefinition —— Bean
  • BeanFactory —— Bean 容器

类 UML 关系图

由于step1只存在两个类,所以 UML 关系非常简单:

20201104001934

目标:

  • 实现基本的 BeanDefinition 类载体以及 BeanFactory 类工厂。

类功能:

BeanDefinition

  • 封装了 bean,在第一部的时候代码还非常简单,就是单纯的封装了一个 Object ,用来保存对象,也就是 Bean。
public class BeanDefinition {

    private Object bean;

    public BeanDefinition(Object bean) {
        this.bean = bean;
    }
    public Object getBean() {
        return bean;
    }
}

BeanFactory

  • 这一步仅仅作为 Bean 的容器,内部封装了一个 Map 用来保存 BeanDefinition
  • Mapput 方法封装了一层,增强了语义化。
public class BeanFactory {
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    public Object getBean(String name) {
        return beanDefinitionMap.get(name).getBean();
    }
    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(name, beanDefinition);
    }
}

测试:

  1. 创建一个 类,作为 Bean 。
  2. 将 Bean 手动封装在 BeanDefinition 中。
  3. 将 BeanDefinition 保存在 BeanFactory 中
  4. 从 BeanFactory 中根据名称获取 Bean,并调用 Bean 中的方法。
// 定义的测试 bean
public class HelloWorldService {
    public void helloWorld(){
        System.out.println("Hello World!");
    }
}

// 测试 beanfactory 的代码
public class BeanFactoryTest {

	@Test
	public void test() {
		// 1.初始化beanfactory
		BeanFactory beanFactory = new BeanFactory();

		// 2.创建一个 BeanDefinition,并且封装一个测试 bean 的实例在 BeanDefinition 中
		BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
    // 3. 将 BeanDefinition 存入 BeanFactory
		beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

    // 4. BeanFactory 通过 name 过去到对应 bean,并调用 bean 中方法
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory
      .getBean("helloWorldService");
    helloWorldService.helloWorld();
    }
}

step2-将bean创建放入工厂

切换到step2:

git checkout step-2-abstract-beanfactory-and-do-bean-initilizing-in-it

类:

  • BeanDefinition
  • BeanFactory
  • AbstractBeanFactory
  • AutowiredCapableBeanFactory

类 UML 图

20201104003227

目标

  • 将 BeanFactory 从类分离为接口、抽象类、实现类 ,通过继承提升程序的复用性,以及层次性。
  • 增强 BeanFactory 的能力,底层通过反射创建对象,避免使用者手动 new 对象

类功能:

BeanDefinition:

相比 step1 的 BeanDefinition,这里封装了更多的 Bean 的元信息,比如 :

  • String beanClassName —— 类的全路径名称,用来使用反射直接构造类的实例,而不是使用 new 去创建类实例
  • Class beanClass —— 对应 Bean 的 Class 对象,获取类的元信息,以及反射时使用。

interface : BeanFactory

不同于 step1 中的 BeanFactory ,step2 为了提升泛用性,直接将其抽象为了接口,所以此时的 BeanFactory 就是一个顶层接口,其中包含2个方法:

  • 根据 name 获取对应的 bean
  • 将 BeanDefinition 放入 BeanFactory 中
  • 这里放入 BeanFactory 中的
public interface BeanFactory {
  Object getBean(String name);
  
  void registerBeanDefinition(String name, BeanDefinition beanDefinition);
}

abstract class : AbstractBeanFactory

作为一种典型的面向对象的编程手法,除了顶层的接口,下一层就是实现了基本功能的抽象类,这个类的意义是为了方便实现类的编写,所有实现类通用的功能被实现在抽象类中。

实现的方法:

  • 根据名称获取 BeanDefinition 中保存的 Bean
  • 将 BeanDefinition 放入 BeanFactory 中的 doCreateBean 作为抽象方法,让子类去实现。
public abstract class AbstractBeanFactory implements BeanFactory {

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    @Override
    public Object getBean(String name) {
        return beanDefinitionMap.get(name).getBean();
    }
		

  	// 根据 BeanDefinition 中保存的 Class 对象,使用反射创建对应 bean 的实例
  	// 将实例存入 BeanDefinition 中
  	// 将 BeanDefinition 存入 BeanFactory 中
    @Override
    public void registerBeanDefinition(String name, BeanDefinition beanDefinition) {
        Object bean = doCreateBean(beanDefinition);
        beanDefinition.setBean(bean);
        beanDefinitionMap.put(name, beanDefinition);
    }

    /**
     * 初始化bean
     *
     * @param beanDefinition
     * @return
     */
    protected abstract Object doCreateBean(BeanDefinition beanDefinition);

}

具体实现类:AutowireCapableBeanFactory

这个类就是真正实现 BeanFactory 功能的类了,这一步中这个类实现了 抽象类中的 doCreateBean 函数

public class AutowireCapableBeanFactory extends AbstractBeanFactory {

    @Override
    protected Object doCreateBean(BeanDefinition beanDefinition) {
        try {
            Object bean = beanDefinition.getBeanClass().newInstance();
            return bean;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这里一个最大的改变就是:

  • 不再用 new 显式创建对象,而是根据类的全路径名使用反射创建对象。

虽然这只是小小的一个改变,但是已经从使用方式上彻底改变了容器的功能,从下面的测试代码的变化就可以体会到这一点。

测试代码:

首先,定义一个测试 bean,代码与之前的相同,就不重复编写了

public class BeanFactoryTest {
  @Test
  public void test() {
    // 1. 创建 BeanFactory 实现类实例
    BeanFactory beanFactory = new AutowireCapableBeanFactory();
    // 2.创建 BeanDefinition
    BeanDefinition beanDefinition = new BeanDefinition();
    // 3.将测试 bean 的全路径名称 存入 BeanDefinition
    beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");
    // 4. 将BeanDefinition 存入 BeanFactory
    beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);
    // 5.获取bean
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
    // 6. 调用 bean 中函数
    helloWorldService.helloWorld();
  }
}

这里关键在于步骤4:

  • steap1 中 构建 BeanDefinition 的方法: BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
  • 这里使用 beanDefinition.getBeanClass().newInstance(); 使用反射构建了 Bean 对象,将构建好的 Bean 对象封装到了 BeanDefinition 中,再将 BeanDefinition 存入了 BeanFactory 的 Map 中,一气呵成。

小结:

step2 最重要的事情就是把创建对象的实例的方式从主动 new 变为了通过反射创建对象的实例,进而可以进行更深层次的封装。

这是封装的一小步,是框架完成的一大步。

3.step3-为bean注入属性

切换到step3:

git checkout step-3-inject-bean-with-property

目标:

  • 为 BeanFactory 中保存的 Bean 进行字段的注入。

新加类:

  • PropertyValue

封装了类中字段与对应的字段的值。

  • PropertyValues

PropertyValue 的容器,一个类有多少个字段就对应多少个 PropertyValue

功能增强:

  • AutowireCapableBeanFactory

增加了通过反射给 Bean 的字段注入属性的功能。

	// 增加了给Bean设置属性的方法,使用反射创建完对象之后就将属性设置到 Bean 的对应字段中
	protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
		for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
      // 根据 propertyValue 保存的字段名,获取到对应字段的 Field 对象,并将 propertyValue 中保存的值赋给 Field
			Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
			declaredField.setAccessible(true);
			declaredField.set(bean, propertyValue.getValue());
		}
	}
  • BeanDefinition

Bean 的元数据增加了 propertyValues ,用来保存 Bean 的属性。

// 多了一个 PropertyValues,保存了这个 Bean 的字段
private Object bean;

private Class beanClass;

private String beanClassName;

private PropertyValues propertyValues;

代码测试:

定义一个测试的 Bean,与之前不同的是这个 Bean 包含一个 String 字段,为了一会测试属性注入

public class HelloWorldService {
	// Bean 的属性,一会被将字段进行注入
  private String text;

  public void helloWorld(){
    System.out.println(text);
  }

  public void setText(String text) {
    this.text = text;
  }
}

主要测试流程:

public class BeanFactoryTest {
	@Test
	public void test() throws Exception {
		// 1.创建 BeanFactory 实例
		BeanFactory beanFactory = new AutowireCapableBeanFactory();

		// 2.创建 BeanDefinition
		BeanDefinition beanDefinition = new BeanDefinition();
		// 3. 设置 Bean 的完整类路径,用来构造实例
		beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");

		// 设置 Bean 的属性,用于后续属性注入
		PropertyValues propertyValues = new PropertyValues();
		propertyValues.addPropertyValue(new PropertyValue("text", "Hello World!"));
        beanDefinition.setPropertyValues(propertyValues);

		// 4.根据完整类路径创建Bean —— 注入Bean属性 —— 将Bean存入BeanDefinition —— 将BeanDefinition存入 BeanFactory
		beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

		// 5.根据Bean名称获取Bean
		HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
		// 6.调用Bean中方法,输出注入的字段值
		helloWorldService.helloWorld();
	}
}

小结

属性注入让Bean的管理向着更加完善的一步迈进。

4.step4-读取xml配置来初始化bean

切换到step4:

git checkout step-4-config-beanfactory-with-xml
git branch <new-branch-name> d68ca4f

目标:

通过 XML 灵活定义 Bean。

新增类:

  • Resource
  • ResourceLoader
  • UrlResource
  • XmlBeanDefinitionReader
  • AbstractBeanDefinitionReader
  • BeanDefinitionReader

类图与功能简单描述:

可以看到这里作者的抽象层次非常清晰: 接口 —— 抽象类 —— 具体实现类 三个层次的关系非常明确。

  • 接口保证扩展性,同时定下核心方法

  • 抽象类封装了具体实现类要用到的数据,以及提供了必要的 get 方法

  • 具体实现类完成功能

这里我重点关注的是 XmlBeanDefinitionReader 这个类,可以说这一步骤的核心功能就是在这个类中完成的,下面看看这个类的具体代码:

  • name : Bean 的名称
  • class : Bean 的全路径名,用来构造实例
  • property : Bean 的属性
    • name :字段名称
    • value :字段对应的值
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

  public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
    super(resourceLoader);
  }

  @Override
  public void loadBeanDefinitions(String location) throws Exception {
    // ① 通过 XML 文件的位置,获取对应的 URL 对象,然后获取 InputStream 输入流
    InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
    // ② 读取 XML 中定义的 BeanDefinition 配置,下面会层层封装逻辑
    doLoadBeanDefinitions(inputStream); 
  }

  protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
    // ① 构建 DocumentBuilderFactory,这个类是 Java 中DOM模式解析器的工厂类
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // ② 创建 DocumentBuilder 
    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    // ③ 真正开始解析 XML 文件,解析结果是 Document 类
    Document doc = docBuilder.parse(inputStream);
    // 开始根据解析后的 Document 对象设置 BeanDefinition 参数
    registerBeanDefinitions(doc);
    inputStream.close();
  }

  public void registerBeanDefinitions(Document doc) {
    // 获取 Element 节点
    Element root = doc.getDocumentElement();
    // 解析 BeanDefinition 参数
    parseBeanDefinitions(root);
  }

  protected void parseBeanDefinitions(Element root) {
    // 获取元素中的节点列表
    NodeList nl = root.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
      Node node = nl.item(i);
      if (node instanceof Element) {
        Element ele = (Element) node;
        processBeanDefinition(ele);
      }
    }
  }

  protected void processBeanDefinition(Element ele) {
    // 查找定义好的节点名称为 name 和 class 的节点的内容
    String name = ele.getAttribute("name");
    String className = ele.getAttribute("class");
    BeanDefinition beanDefinition = new BeanDefinition();
    // 处理 XML 中配置的 Bean 字段属性
    processProperty(ele,beanDefinition);
    beanDefinition.setBeanClassName(className);
    // 获取封装 BeanDefinition 的 Map,并将对应的类名和BeanDefinition存入其中
    getRegistry().put(name, beanDefinition);
  }

  private void processProperty(Element ele,BeanDefinition beanDefinition) {
    // 根据定义好的 property 节点 和 name、value 元素 构建 propertyValue 并保存在 BeanDefinition 中
    NodeList propertyNode = ele.getElementsByTagName("property");
    for (int i = 0; i < propertyNode.getLength(); i++) {
      Node node = propertyNode.item(i);
      if (node instanceof Element) {
        Element propertyEle = (Element) node;
        String name = propertyEle.getAttribute("name");
        String value = propertyEle.getAttribute("value");
        beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name,value));
      }
    }
  }
}

测试

  1. 创建一个测试Bean
  2. 创建一个 xml 文件来配置 Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <!--定义一个bean-->
    <bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
        <property name="text" value="Hello World!"></property>
    </bean>

</beans>

测试主体逻辑:

public class BeanFactoryTest {
  @Test
  public void test() throws Exception {
    // 1.构建 XmlBeanDefinitionReader
    XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
    // 2. 传入配置文件的 uri ,对其进行解析,解析后生成 BeanDefinition 
    xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

    // 3.初始化BeanFactory
    BeanFactory beanFactory = new AutowireCapableBeanFactory();
    // 4.将被保存在 xmlBeanDefinitionReader 中的解析 xml 生成的 BeanDefinition 保存到 BeanFactory 中
    for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
      beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
    }
    // ---------- 读取 XML 并解析 Bean 配置以及属性注入逻辑完成 ----------
    // 5.获取bean并调用对应的方法
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
    helloWorldService.helloWorld();
  }
}

小结

到了这一步,已经可以动态配置 Bean 了,又灵活了不少。

5.step5-为bean注入bean

切换到step5:

git checkout step-5-inject-bean-to-bean

这一步骤要解决一个重要的问题: Bean 循环依赖

这个问题我之前在面试题中看到过,问的是「Spring 是如何处理循环依赖的」。其实那个问题我之后也没有去仔细深究过,所以一直是弱点之一。

这一步骤刚好从0实现的时候要去解决这个问题,那就深入的去了解并实践一下。

新增类:

  • BeanReference —— 用来保存 Bean 的名称和 Bean 中对其他类的依赖的引用,用来解决循环依赖问题。

我们定义一个BeanReference,来表示这个属性是对另一个bean的引用。这个在读取xml的时候初始化,并在初始化bean的时候,进行解析和真实bean的注入。

public class BeanReference {
  // ... get/set ... 省略 //
  private String name;
  private Object bean;
}

那么首先问题来了:什么是循环依赖

之前注入的 Bean 中的字段并没有是另一个类的引用这种情况,之前注入的例子中都是字符串这样的字段。

而如果一个类中持有另一个类,这时候就存在依赖关系。

而如果几个类之间的依赖形成一个圈,如下图(网上找的):

image-20201105002604752

甚至循环依赖自己:

image-20201105002734649

上面的情况就是循环依赖了。

那么看看作者在这里是怎么处理这种情况的:

首先找到处理循环引用的地方,既然与类的字段注入有关,那肯定在 BeanFactory 中:

// AutowireCapableBeanFactory#applyPropertyValues ---> steap4 属性注入
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
  for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
    Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
    declaredField.setAccessible(true);
    declaredField.set(bean, propertyValue.getValue());
  }
}



// AutowireCapableBeanFactory#applyPropertyValues ---> steap5 属性注入
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
  for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
    Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
    declaredField.setAccessible(true);
    Object value = propertyValue.getValue();
    // 判断 value 是否属于类引用类型,如果是的话,就注入其他类的引用
    if (value instanceof BeanReference) {
      BeanReference beanReference = (BeanReference) value;
      value = getBean(beanReference.getName());
    }
    declaredField.set(bean, value);
  }
}

可以看到,step5 对比 step4 在注入时增加了对 value 类型的判断,结合本次引入的专门保存类引用的 BeanReference ,来注入 Bean 中对其他类的引用。

并且这里使用了懒加载模式,将 Bean 的真正构建放在调用 getBean ,的时候才会执行,这样在注入 Bean 的时候,如果属性对应的 bean 找不到,就先创建。

总的原则是: 先创建,后注入。 这样就不会存在两个循环依赖的bean创建死锁的问题。

XmlBeanDefinitionReader:

这里也要对 xml 中新增的引用情况进行处理:

  • 增加了 对 "ref" 节点的处理
// XmlBeanDefinitionReader#processProperty
private void processProperty(Element ele, BeanDefinition beanDefinition) {
  NodeList propertyNode = ele.getElementsByTagName("property");
  for (int i = 0; i < propertyNode.getLength(); i++) {
    Node node = propertyNode.item(i);
    if (node instanceof Element) {
      Element propertyEle = (Element) node;
      String name = propertyEle.getAttribute("name");
      String value = propertyEle.getAttribute("value");
      if (value != null && value.length() > 0) {
        beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
      } else {
        String ref = propertyEle.getAttribute("ref");
        if (ref == null || ref.length() == 0) {
          throw new IllegalArgumentException("Configuration problem: <property> element for property '"
                                             + name + "' must specify a ref or value");
        }
        BeanReference beanReference = new BeanReference(ref);
        beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
      }
    }
  }
}

测试

既然提到循环依赖,那么就一定不是单个 bean 。

bean1 ---> HelloWorldService

public class HelloWorldService {
  private String text;
	// 持有 bean2 OutputService 的引用
  private OutputService outputService;

  public void helloWorld(){
    outputService.output(text);
  }

  public void setText(String text) {
    this.text = text;
  }

  public void setOutputService(OutputService outputService) {
    this.outputService = outputService;
  }
}

bean2 ---> OutputService

public class OutputService {
		// 持有bean1 HelloWorldService 的引用
    private HelloWorldService helloWorldService;

    public void output(String text) {
        Assert.assertNotNull(helloWorldService);
        System.out.println(text);
    }

    public void setHelloWorldService(HelloWorldService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }
}

配置 xml(省略xml头) : 定义了两个bean,并且 bean 中字段都包含对方的引用

<bean name="outputService" class="us.codecraft.tinyioc.OutputService">
  <property name="helloWorldService" ref="helloWorldService"></property>
</bean>

<bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
  <property name="text" value="Hello World!"></property>
  <property name="outputService" ref="outputService"></property>
</bean>

测试主流程:

  • 这里有两个测试方法
    • testLazy —— 测试延迟加载,直到 getBean 时 才会正在调用创建 Bean 的流程
    • testPreInstantiate —— 显示调用初始化 Bean 的流程
public class BeanFactoryTest {
    @Test
    public void testLazy() throws Exception {
        // 1.读取配置
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(
                new ResourceLoader());
        xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

        // 2.初始化BeanFactory并注册bean
        AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader
                .getRegistry().entrySet()) {
            beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(),
                    beanDefinitionEntry.getValue());
        }

        // 3.获取bean
        HelloWorldService helloWorldService = (HelloWorldService) beanFactory
                .getBean("helloWorldService");
        helloWorldService.helloWorld();
    }
		
    @Test
    public void testPreInstantiate() throws Exception {
        // 1.读取配置
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(
                new ResourceLoader());
        xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

        // 2.初始化BeanFactory并注册bean
        AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader
                .getRegistry().entrySet()) {
            beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(),
                    beanDefinitionEntry.getValue());
        }

        // 3.初始化bean
        beanFactory.preInstantiateSingletons();

        // 4.获取bean
        HelloWorldService helloWorldService = (HelloWorldService) beanFactory
                .getBean("helloWorldService");
        helloWorldService.helloWorld();
    }
}

但是我这里仔细看了一下,从而产生了一个 问题 :就算是显式先调用了 预初始化Bean流程,与不调用直接 getBean ,最终都落到了 getBean 这个流程中。

// 显示初始化bean
beanFactory.preInstantiateSingletons();

// preInstantiateSingletons 
// 遍历 BeanFactory 中的所有 BeanDefinition 并逐个调用 getBean
public void preInstantiateSingletons() throws Exception {
  for (Iterator it = this.beanDefinitionNames.iterator(); it.hasNext();) {
    String beanName = (String) it.next();
    getBean(beanName);
  }
}


总结

核心的底层机制还是增加了对不同 xml 标签的处理流程,不多的代码让我很轻易就能找到底层的逻辑,还是对学习Spring机制有很大的帮助的。

6.step6-ApplicationContext登场

切换step6:

git checkout step-6-invite-application-context

之前看小马哥的课程,用了相当多的篇幅来介绍 IoC 容器,其中有这么一个话题:

  • BeanFactory 和 ApplicationContext 哪个才是 IoC 容器?

Spring 官方网站给出的结论是 BeanFactory 是底层的 IoC 容器, ApplicationContext 是它的完全超集,扩展了更多的功能,用起来更加方便。

这一步作者就引入了 ApplicationContext,并且在引入之后确实比之前要方便了不少。

新增加的类:

  • ApplicationContext —— 继承 BeanFactory ,接口。
  • AbstractApplicationContext —— 实现 ApplicationContext的抽象类,实现 getBean 方法,定了了 refresh 方法
  • ClassPathXmlApplicationContext —— ApplicationContext 的真正实现类。

ApplicationContext 内部逻辑

ApplicationContext 是 BeanFactory 的扩展,有更多的功能,那么其中肯定封装着很多具体的逻辑。

构建 ClassPathXmlApplicationContext —— 构造函数 :

public ClassPathXmlApplicationContext(String configLocation) throws Exception {
  this(configLocation, new AutowireCapableBeanFactory());
}

public ClassPathXmlApplicationContext(String configLocation, AbstractBeanFactory beanFactory) throws Exception {
  // 创建 BeanFactory 
  super(beanFactory);
  this.configLocation = configLocation;
  // 封装着初始化 BeanFactory 的具体逻辑
  refresh();
}

@Override
public void refresh() throws Exception {
  // 创建 XML 读取器
  XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
  // 读取 XML 并获取 Bean 与 Bean 的属性
  xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
  for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
    // 创建 Bean 并注入属性
    beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
  }
}

看完了 refresh()方法之后,大概想到了为什么在 Spring 中使用 ApplicationContext 一定要调用这个方法,这个方法才是真正去调用初始化容器的逻辑的开关。

测试:

使用 ApplicationContext 获取 XML 中配置的 bean:

可以看到整个过程只有三步,和我们平时使用 Spring 的体验已经基本一致了。

public class ApplicationContextTest {
  @Test
  public void test() throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
    HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
  }
}

总结

至此,Spring IoC 容器的核心功能已经实现。这只是第一遍学习,后续随时会继续印证学习,虽然代码只有几百行,但是真的对初学者去了解 Spring 的核心机制很有帮助

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。