【重写SpringFramework】第一章beans模块:IOC容器概述(chapter 1-1)

1. 前言

Spring 框架是 Java 语言中非常流行的开源框架,用于简化企业级应用的开发。其中,IOC(控制反转)和 AOP(面向切面编程)是 Spring 框架中的两个核心概念。本章我们重点介绍 IOC 的原理和实现,有关 AOP 的内容将在第二章 aop 模块进行介绍。

IOC 的全称为 Inversion of Control,通常翻译为「控制反转」。控制反转是一种设计原则,其主要目的是降低代码之间的耦合度。在 Spring 框架中,IOC 容器负责管理所有对象,任意两个对象之间不需要直接联系,从而降低了依赖关系。如果某个对象依赖另一个对象,IOC 容器自动寻找依赖项并注入给该对象,这一过程称为依赖注入(Dependency Injection)。

2. 控制反转

为了进一步理解什么是控制反转,我们用日常生活中的事例来说明。甲发现 A 餐厅不错,经常去吃饭。乙是甲的朋友,在甲的介绍下,乙也经常光顾 A 餐厅。丙经常去 B 餐厅吃饭,但他不认识甲和乙,因此不会去 A 餐厅,同理甲和乙也不会去 B 餐厅。到了互联网时代,线上平台出现了,A 餐厅和 B 餐厅都在平台上注册,甲、乙、丙可以在平台上浏览信息,然后选择一家心仪的餐厅。

在传统模式下,口耳相传是最有效的传播方式,获取信息的成本在于构建人际关系。在互联网模式下,信息的传播发生了颠覆性的改变,任何人都可以方便快捷地获取所需要的信息。造成这一变化的原因是什么,我们可以从供需双方的组织结构来分析。

传统模式下供需双方的关系被割裂为若干子系统,子系统之间的交流异常困难。每个子系统都可以看做是数据结构中的图(graph),不同的需方节点到供方节点的权重(weight)也不一样。甲的权重为 1,乙的权重为 2,依次类推。而在互联网模式下,系统是一个整体,平台居中维系着供需双方的联系,所有需方节点到供方节点的权重都是 1。由此可见,系统的完备性以及信息传递的低权重是提高效率的两大因素

回过头来再看,所谓控制是指寻找餐厅的行为,一开始是由消费者所掌握的。当线上平台出现后,餐厅在平台上注册,可以认为是由平台完成了寻找餐厅的行为,消费者只需要做出选择即可(被动地接收信息)。至此,我们已经对控制反转的概念有了初步认识,为了构建一个完备且高效的系统,还需要探寻其背后更深层次的理论依据。

3. 迪米特法则

迪米特法则(Law of Demeter)又称最少知识原则,其基本表述为只和最亲密的朋友交流。也就是说,如果两个类彼此之间不需要直接通信,那么就不要建立联系。在软件系统中,各个类之间的引用或依赖关系好比它们的人脉,我们应当剔除掉不必要的人脉,只维护最小的朋友圈。

概念是抽象的,我们用一个例子来说明。比如 B 创建了 A 对象,如果 C 想得到 A 对象,就需要依赖 B 对象。根据迪米特法则,应该尽可能少地与其他类打交道,对于 C 来说,最好能绕过 B 直接得到 A 对象。这时,IOC 容器就起到了线上平台的作用。所有的对象都注册到容器里,一个类想要什么样的对象只需要告诉容器,无需关心目标对象是如何创建的。由此可见,IOC 的核心就是将创建对象和管理对象的控制权交出去(由类转移到容器),这是控制反转的真正含义。

在一个应用程序里,可能存在成百上千乃至更多的对象,维护它们之间关系是一件费力不讨好的事情。再加上系统的不断迭代,开发人员的变更,在诸多因素的影响下,事情只会朝着更坏的方向发展。IOC 容器的出现不啻于一场革命,使得混沌无序的系统变得简洁而富有条理,其重要性可见一斑。换一个角度来看,奥卡姆剃刀原理认为如无必要,勿增实体,那么 IOC 容器就是「必要的实体」。

需要指出的是,外卖和购物平台的信息流通方式是单向的,商家提供产品或服务,用户负责消费。IOC 容器更类似二手置换平台,每个用户都既是买家,也是卖家,二手物品的流通是双向的。对于 IOC 容器来说,任何一个对象都可以依赖其它对象,也可以被其它对象所依赖。

4. 简单工厂模式

时至今日,Spring 框架已经是一个功能强大、内容丰富的庞然大物,但最核心最基础的部分仍是一个 IOC 容器,通常也称为 Spring 容器。一般来说,Spring 容器包括狭义和广义两个方面,狭义的是指 BeanFactory, 广义的是指 ApplicationContext。本章我们只关注 BeanFactory,后者将在第三章 context 模块进行讨论。

顾名思义,BeanFactory 就是一个创建 Bean 的工厂。工厂的概念来自一种常见的设计模式,即简单工厂模式。对于一个类来说,最大的痛点是如何获得目标对象,至于对象是如何创建的,实际上并不关心。简单工厂模式的特点是屏蔽了产品的生产细节,交到消费者手中的是成品。正因如此,控制反转是在简单工厂模式的基础上实现的。

简单工厂模式的结构并不复杂,由三个角色构成:

  • Creator 表示工厂角色,负责生产产品
  • Product 是抽象产品角色,定义了产品的特征
  • ConcreteProduct 是具体产品角色,表示最终生产的成品

Spring 所使用的工厂模式是标准结构的变体(variant),其中工厂角色被拆分成了 BeanFactory 接口和 DefaultListableBeanFactory 实现类,getBean 方法是创建产品的核心方法。由于工厂只生产一种产品,因此抽象产品角色和具体产品角色合并起来,这里的 Bean 不是具体的某个类,可以是任意对象。

实际上,BeanFactory 的继承关系远比类图中的复杂,getBean 方法也不是 DefaultListableBeanFactory 实现的。我们只是讨论 BeanFactory 是如何实现简单工厂模式的,进行了一定的简化。

5. 依赖注入

以上详细讨论了控制反转以及相关的一些问题,那么依赖注入又是怎么一回事。回到吃饭的问题,用户既然可以在线上平台寻找餐厅,但还是要亲自前往某个餐厅用餐。那么能不能直接在线上下单,餐厅做好后直接送上门?当然是可以的,由此衍生出了外卖业务。反正不打算自己做饭了,干脆连馆子也不用去了,直接让外卖送到手上。同样地,一个对象只需要告诉容器需要什么样的依赖项,由容器自行寻找依赖项并赋给当前对象,这一过程就称为依赖注入。

在下面的示例代码中,分别演示了不使用依赖注入和使用依赖注入的情况。在不使用依赖注入的情况下,我们首先要拿到 BeanFactory 的实例(暂时先不管是如何拿到的),然后手动调用 getBean 方法得到目标对象。整个过程的代码量不算大,但考虑到依赖对象的情况广泛存在于应用程序中,难免会出现大量重复的代码。对于一个好的软件系统来说,应该避免这种情况的发生。

java 复制代码
//示例代码:不使用依赖注入
public class Foo {
    private BeanFactory beanFactory;
    private Bar bar;

    public Foo() {
        this.bar = this.beanFactory.getBean("bar", Bar.class);
    }
}

当我们使用了依赖注入,只需要声明一个注解,Spring 容器就会将所需的对象注入到指定字段上。代码变得极致简洁,所有的细节都被隐藏了,这种一站式的快捷用法对开发者来说无疑是非常友好的。

java 复制代码
//示例代码:使用依赖注入
public class Foo {
    @Autowired
    private Bar bar;
}

依赖注入可以看做是控制反转的补充,如果说交出创建对象的控制权是第一层反转的话,那么由容器注入依赖对象就是第二层反转。Spring 容器解决了信息流通的问题,依赖注入则使得获取信息(依赖对象)的过程变得十分简单,以至于感受不到 Spring 容器的存在。这是极为高明的策略,就好比每时每刻都要呼吸空气,却感受不到空气的存在一样。

故《道德经》有言:「上德不德,是以有德」「功成事遂,百姓皆谓我自然」。如何理解?统治者最好的德行是不显现出来,人民做成了任何事都认为这是自然而然的。

将古代圣贤的语言换成编程领域的表述就是低侵入性,也就是说第三方框架应该尽可能的降低对源代码的影响,最好感知不到第三方框架的存在。低侵入性是 Spring 框架始终秉持的核心理念之一,我们将在接下来的章节详细讨论。

6. 总结

软件系统和人类社会非常相似,每个对象都有自己的资源,也有属于它的人脉和朋友圈。当一个对象需要某个资源时,必须想办法与该资源的拥有者建立联系。这种联系可能是直接的,也可能是间接的,不管怎么说都会使对象之间的依赖关系变得更加复杂。这是一个熵增的系统,在不引入外力的情况下(比如进行大规模重构),会变得越来越混乱无序。IOC 容器的作用是将所有的资源集中起来,这就导致了资源控制权的易主,也就是所谓的控制反转。这样做的好处是显而易见的,对象获取资源变得简单而直接,再也不需要苦心经营复杂的依赖关系。

控制反转的理论基础是迪米特法则,IOC 容器的出现使得每个对象都能轻装上阵,剔除了不必要依赖关系,降低了整个系统的复杂度。消费者直接通过 IOC 容器获取资源,不再关心对象是如何创建的,因此 Spring 框架在实现 IOC 容器时使用了简单工厂模式。这一模式的特点是隐藏了产品的生产细节,交到消费者手中的已经是成品,从而解决了核心痛点。

接下来介绍了什么是依赖注入,简单说来依赖注入就是控制反转的补充。创建对象是主动行为,获取资源(依赖对象)也是主动行为。既然 IOC 容器控制了对象的创建,干脆连检索资源的工作一并承接过来,反过来将依赖项交给有需要的一方。控制反转和依赖注入的关系用一个形象的比喻,就好比堂食和外卖的区别。具体来说,餐厅提供食物就是控制反转(不用自己做饭),把外卖送到消费者手上则是依赖注入。


扫码关注公众号【Java编程探微】,查看更多精彩内容

相关推荐
救救孩子把9 分钟前
深入理解 Java 对象的内存布局
java
落落落sss11 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节17 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭24 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
我行我素,向往自由30 分钟前
速成java记录(上)
java·速成
一直学习永不止步36 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明36 分钟前
面试知识储备-多线程
java·面试·职场和发展
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
2401_854391081 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端