什么是 IoC & DI#
这是一个很不好回答的问题,也许应当进行一些 STFW、观看一些示例之后得到一个粗浅的理解。
至于更 “正确”,更 “严谨” 的概念定义,则应该在实际使用、并了解其实现之后,也许会得到一些更成熟的看法。
这里务实的、 opnioned 的看法是这样的:
InverseOfControl,也就是控制反转,结合框架简单的理解就是将对象的创建管理由程序员重复手动编码控制交由一个容器框架进行自动控制。
Dependencies Inject,也就是依赖注入,与 IoC 是一体两面的。在这个语境下,容器框架提供了给出对象的能力后,coder 只要在需要特定对象的时候去get(),对于 AB 两个对象,A 依赖 B,既然 B 对象的创建可以交个 Container,那么 A 的创建也可以交给容器,而 A 依赖 B,这个 B 可以由容器创建,然后赋值给 A。在 A 的视角看:尽管自己依赖 B,但 B 不需要由 A 自己创建,由外部(A 的外部)在创建 / 使用 A,注入 B。
下文所提到的 IoC 是 IoC 容器框架,一般囊括了 IoC、DI 的职责。
为什么要 IoC#
不妨先看看不用 IoC 会怎么做
- 当程序员需要使用一个对象时,需要去手动创建,或者到约定的地方查找
- 对于大量对象的手动控制会增加巨大的心智负担
- 手动控制有着很高的上限,且逻辑由程序员控制,debug 更加容易
有了 IoC 之后,就可以解决上面的两个问题。 - 通过容器管理大量对象,降低开发人员的心智负担。
- 无需手动创建,需要使用时只要描述自己所需的对象是什么样的。
当然,也带来了一个问题,想比于手造,对容器中的对象进行 debug 时并不直观。
IoC 的核心就是将一些特定对象的构建 / 初始化等样板代码交给 IoC 容器。
使用者只需要告诉 IoC 容器两件事。
- 我需要什么样的对象,让 IoC 容器给我(依赖注入)
- 如何构建这样的对象,告诉 IoC 容器如何构建(工厂方法)
因此,一个 IoC 容器需要做的事情也就是构建和提供对象。
Bean#
在继续之前,有必要以狭义的方式了解一下 Bean 是什么,以方便后续理解。
对于一个普通的 Class。本身是一个 POJO(瞧这复杂而丑陋的定义、别名、简写),通过new 这样的关键字自行创建。
但是如果这样的对象、类交给 IoC 容器进行管理,这些被 Spring IoC 容器所管理的对象。我们就可以称为 Spring Bean。
IoC 的职责划分#
结合前面所述,可以得到两种职责
- IoC 框架在需要的时候创造使用者需要的对象,实现控制反转。
- 存储已经造好的对象,用于依赖注入。
对于第一种职责,表现为 Factory,也就是创建对象的工厂。一方面,要负责创建 Bean,createBean,另外一方面,工厂要知道,创建 Bean 的方式(比如用什么类名、各个属性赋什么值)。这可以通过一个 BeanDefinition 对象进行描述,要有一个小仓库存储这样的 BeanDefinition。
对于第二种职责,表现为 Container / Registry,也就是一个存储 Bean 的仓库。
等等,似乎有哪里不太对,工厂可以生产对象、存储生产的方式,生产的成品对象可以存储到仓库中去,但是还缺少了原料。工厂需要原料,需要在一个地方去采购(Load)原料(BeanDefinition),表现为 Loader。
这么看最终用户还是要自行提供原料啊,优越性在哪?正是在这,用户只需要提供原料,不再需要成品了。原料简单啊、完全可以通过声明式的方式进行提供。
interface Factory {
fun createBean(beanName: String): Any
fun registryBeanDefinition(beanName: String, beanDef: BeanDefinition): Unit
}
interface Loader {
fun loadBeanDefinition(beanDefSource: Source): List<BeanDefinition>
}
interface Container {
fun getBean(name: String): Any
fun getBean(clazz: Class): Any
fun getBean(name: String, clazz: Clazz): Any
// maybe
fun registerBean(name: String, obj: Any): Any
}
综合起来就是
- Loader 从一个地方加载 BeanDefinition,放入 Factory。
- Factory 生产 Bean 放入 Container。
- Container 提供给使用者,
getBean这样简单的 API。
更近一步,如果所有的对象获取都通过 Container,那么,对于任意一个依赖于 B 类 的 A 对象,在被创建前,需要先在仓库中检查是否存在 B 类对象,如果不存在,就转而去创建 B 类对象、如果存在,就将 B 类对象作为依赖注入到 A 类对象中去。
既可以将其视为这样一张图
loading...
一些增强,BeanPostProcessor#
在 BeanFactory 创建 Bean 的过程中,可以按需对 Bean 进行一些修改,比如对一些符合 PlaceHolder 规则的属性值进行替换、为 Bean 创建代理。
比如可以设计这样一个接口,可以通过 Hook BeanFactory 创建 Bean 的不同阶段,对 Bean 进行修改。
interface BeanPostProcessor {
fun beforeInstantiation(beanClass: Class<*>, beanName: String): Any? = null
fun afterInstantiation(bean: Any, beanName: String): Boolean = true
fun beforeApplyPropertyValues(pvs: PropertyValueRegistry, bean: Any, beanName: String): PropertyValueRegistry? = null
fun beforeInitialization(bean: Any, beanName: String): Any? = bean
fun afterInitialization(bean: Any, beanName: String): Any? = bean
}
IoC 对外的 API#
而上面这些在使用者眼中可以屏蔽其中各种内部概念,进一步整合成 Context。
interface Context {
fun getBean(name: String): Any
fun getBean(clazz: Class): Any
fun getBean(name: String, clazz: Clazz): Any
}
IoC 容器框架向使用者作出一个承诺(提供 API):
使用者不需要自己去构建一个对象,也不需要知道怎么去构建一个对象,它唯一需要的是用一个简短的方式去描述,自己需要什么对象(比如对象名、对象的 Class),并且通过声明式的方式提供一些构造目标对象的原料。
当需要使用时,使用者只需要给出目标对象的名称 / 类型等 metainfo,容器会给出一个这样的对象(给不了怎么办?大不了就报错 xD)
实践上,Spring 的 Context 一人身兼多职,直接继承自各种 BeanFactory,同时保有仓库、加载器的功能。
值得注意的是,尽管 Spring 的 Context 它功能完善、非常实用,但是这种继承的方式极大增加了代码理解成本,在各种迭代的修修补补中,更近一步提高了理解其设计意图的难度。