读书笔记-《Spring技术内幕》(一)IoC容器的实现

如今已是2024年,掌握 Spring 早已是 Java 从业人员的基本要求。

Spring 帮我们屏蔽掉了许多繁琐的工作,使我们可以把重心放在业务逻辑上。但也因此,要深刻体会到 Spring 带来的便捷性,反倒需要与没有使用 Spring 时作对比。为此,我特地翻了下上大学时用 JaveWeb 开发的项目(无框架),希望能让大家更好理解。


01

Spring IoC 器概述

Spring 的架构图如上所示,IoC 与 AOP 都是 Spring 的核心模块,其中 IoC 更是 Spring 的基石。

IoCInverse of Control,控制反转。从(对象 -> 对象)变成了(对象 -> 容器 -> 对象),即控制对象的方式被反转了

DI:Dependency Injection,依赖注入。即由容器注入这个依赖关系

IoC 是思想,而 DI 是具体实现手段。这里放上代码更加直观。

上面的代码中,LoginServlet 直接去 new UserDao,与 UserDao 强耦合,如果 UserDao 的构造函数发生变化,此处的代码也需要调整;而下面的代码,UserServiceImpl 通过注解获取 UserDao,UserDao 的管理交给了容器,从而实现了解耦

当然,这只是个简单的例子,我们可以通过工厂模式实现类似的功能。换句话说,Spring IoC 可以理解为一个非常强大的工厂。


02

BeanFactory 与 ApplicationContext

1.整体设计

BeanFactory:定义了 IoC 容器最基本的形态,提供了 IoC 容器应该遵守的服务契约。

ApplicationContext:IoC 容器的高级形态,除了应该有的基本 IoC 功能外还支持应用事件,不同信息源等高级特性。

BeanDefinition:就是 POJO 类在 IoC 容器的抽象,这些 Java 类成为了容器内部的数据结构,让容器可以轻松的使用和管理它们。

上图为 IoC 容器的核心接口设计图。

以 BeanFactory 作为核心,第一条设计路径为左侧绿色的 HierarchicalBeanFactory 到 ConfigurableBeanFactory。在这条路径中,定义了基本方法如 getBean(),双亲 IoC 容器的管理功能 get/setParentBeanFactory(),以及配置 Bean 后置处理器的 addBeanPostProcessor() 等。

以 ApplicationContext 作为核心,第二条设计路径为右侧橙色的从 BeanFactory 最终落到 WebApplicationContext、ConfigurableApplicationContext 的链路。ApplicationContext 继承了 MessageSource、ResourceLoader、ApplicationEventPublisher 接口,它不但有 BeanFactory IoC 容器的基本功能,还拥有了支持不同信息源、访问资源、支持应用事件等高级特性。

通过 BeanFactory 接口的 getBean 方法,可以通过名字来取得 IoC 容器中的 Bean。基于此用户可以执行以下操作:

  • containsBean() 判断是否有存在指定名字的 Bean

  • isSingleton() 判断是否单例

  • isPrototype() 判断是否原型

  • isTypeMatch() 判断类型是否匹配

  • getType() 获取类型

  • getAliases() 获取别名

2.以 XmlBeanFactory 为例

XmlBeanFactory 顾名思义是以读取 XML 文件方式定义的 BeanDefinition 的 IoC 容器。

public class XmlBeanFactory extends DefaultListableBeanFactory {
    
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException{
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

}

查看源码,我们可以清楚地看到,在 XmlBeanFactory 中初始化了一个 XmlBeanDefinitionReader 对象,在 XmlBeanFactory 的构造函数中,那些 XML 形式的资源是由 Reader 来加载的。

如果以编程式使用,那么就是这样:

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

我们可以更为直观地描述步骤:

  • 创建 IoC 配置文件的抽象资源。

  • 创建一个 BeanFactory。

  • 创建一个 BeanDefinition 的读取器。

  • 让读取器去读取资源,并完成 Bean 的载入和注册。

至此就可以使用 Bean 了。


03

Spring IoC 容器的初始化

在前面编程式使用的例子中我们可以看到,初始化包括了 BeanDefinition 的 Resource 定位、载入和注册三个过程。

由于类名长,抽象接口多,通过文字或图片学习还是比较晦涩,建议在IDEA里跟着方法过一遍。

1.Resource 定位过程

下面以 FileSystemXmlApplicationContext 为例,流程大致如下:

  • FileSystemXmlApplicationContext 构造函数调用 this.refresh()(注意这个 refresh 可以视作整个容器的初始化方法)

  • AbstractApplicationContext.refresh() 调用 this.obtainFreshBeanFactory()

  • AbstractApplicationContext.obtainFreshBeanFactory() 调用 this.refreshBeanFactory(),也就是 AbstractRefreshableApplicationContext.freshBeanFactory()

  • AbstractXmlApplicationContext.loadBeanDefinition()

  • AbstractBeanDefinitionReader.loadBeanDefinition() 注意这里有多个重载方法

  • GenericApplicationContext.getResource()

  • DefaultResourceLoader.getResource()

  • FileSystemXmlApplicationContext.getResourceByPath() 根据路径生成一个 FileSystemResource 并返回

如果是其他的 ApplicationContext,会对应生成其他种类的 Resource,例如 ClassPathResource、ServletContextResource。

2.BeanDefinition 载入

把用户定义好的 Bean 表示成 IoC 容器内部的数据结构,即 BeanDefinition。

下面以 DefaultListableBeanFactory 为例,流程大致如下:

  • AbstractApplicationContext.refresh()

  • AbstractRefreshableApplicationContext.createBeanFactory()

  • AbstractRefreshableApplicationContext.loanBeanDefinition()

  • BeanDefinitionParserDelegate.parseBeanDefinitionElement()

3.IoC 容器注册 BeanDefinition 

将 BeanDefinition 注入到一个 HashMap 中去。

在 DefaultListableBeanFactory 中我们可以看到一个叫 beanDefinitionMap 的成员变量,类型为 ConcurrentHashMap 。流程大致如下:

  • XmlBeanDefinitionReader.loanBeanDefinition()

  • DefaultBeanDefinitionDocumentReader.processBeanDefinition()

  • DefaultListableBeanFactory.registerBeanDefinition() 更新 beanDefinitionMap


04

Spring IoC 容器的依赖注入

依赖注入在用户第一次向 IoC 容器索要 Bean 的时候触发,当然,也可以在 BeanDefinition 信息中通过 lazy-init 属性来让容器完成对 Bean 的预实例化。

下面以 DefaultListableBeanFactory 为例,流程大致如下:

  • DefaultListableBeanFactory 调用AbstractBeanFactory.doGetBean()

  • AbstractAutowireCapableBeanFactory.createBean()

  • SimpleInstantiationStrategy.instantiate()

  • AbstractAutowireCapableBeanFactory.populateBean()

  • AbstractAutowireCapableBeanFactory.applyPropertyValues()

  • BeanDefinitionResolver.resolveReference()


05

其他特性

1.容器初始化和关闭过程

下面以 FileSystemXmlBeanFactory 为例,流程大致如下:

  • FileSystemXmlBeanFactory 调用 AbstractApplicationContext.prepareBeanFactory()

  • AbstractApplicationContext.addPropertyEditorRegistrar()

  • AbstractApplicationContext.addBeanPostProcessor()

  • AbstractApplicationContext.doClose()

  • FileSystemXmlBeanFactory.closeBeanFactory()

关于 IoC 容器中 Bean 的生命周期:

  • Bean 实例的创建

  • 为 Bean 实例设置属性

  • 调用 Bean 的初始化方法

  • 应用可以通过 IoC 容器使用 Bean

  • 当容器关闭时,调用 Bean 的销毁方法

2.自动装配的实现

下前面提到的例子多是以 XML 配置 Bean 的形式,实际工作中使用注解的占多数。配置好 autowiring 属性,IoC 容器会使用反射自动查找属性的类型或者名字。

关于这块的具体实现,可参考 AbstractAutowireCapableBeanFactory.populateBean()。


原文链接:读书笔记-《Spring技术内幕》(一)IoC容器的实现

原创不易,点个关注不迷路哟,谢谢!

文章推荐:如何提高核心竞争力读书笔记-《我的互联网方法论》读书笔记-《银行4.0》学习笔记-催收业务

  • 32
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值