浅谈 IOC
预备知识
反射
反射指程序可以访问、检测和修改它本身状态或行为的一种能力
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息
Class 类
类名为 Class 的类,反射用,本身也是 Object 的子类
JVM 持有的每个Class
实例都指向一个数据类型(class
或interface
)
一个Class
实例包含了该class
的所有完整信息
个人理解:如果将 class 看作用 描述 实现多到一的抽象,那么Class
就是 class
的 class
JVM 运行机制
动态加载 class
,JVM 在第一次读取到一种 class
类型时,将其加载进内存
每加载一种 class
,JVM 就为其创建一个 Class
类型的实例,并关联起来
由于 JVM为 每个加载的 class
创建了对应的 Class
实例,并在实例中保存了该 class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个 Class
实例,我们就可以通过这个Class
实例获取到该实例对应的 class
的所有信息。
这种通过 Class
实例获取 class
信息的方法称为反射(Reflection)
个人理解:从输入输出的相对关系理解,对于程序员来说,class
是输出,对于 JVM 来说,class
是输入,JVM 要做的事情就是处理 class
。class
那么多,还支持自定义,有什么方法能对他们进行统一处理呢?
JVM 采用的方法是引入 Class
,利用Class
将class
进行再组织(也可理解为统一结构化、描述),可以把Class
看做对 class
的抽象,所以说 Class
是 class
的 class
对任意的一个Object
实例,只要我们获取了它的Class
,就可以获取它的一切信息。
反过来想,Class
相当于描述了 class
(毕竟Class
是 class
的 class)
我们就可以利用描述先验地使用一个当时不在场的类,在运行时再往回找这个类(先写剧本再找演员)
描述是类思想(多对一抽象思想)的一种实现手段,实现了多对一的抽象
Class
- Field
- Method
- Constructor
字段 Filed
个人理解:字段就是按照访问限制划分的属性集合,个人感觉本质上直接理解为属性也没多大问题
方法 Method
类的方法对象
构造器 Constructor
单纯的字段和方法本质上都是零碎的信息(零件),要利用构造器将它们组装起来才能成为可用的类(机器)
安全问题
如果使用反射可以获取 private
字段的值,那么类的封装还有什么意义?
答案是正常情况下,我们总是通过 p.name
来访问 Person
的 name
字段,编译器会根据 public
、protected
和 private
决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)
可能会失败。如果 JVM 运行期存在 SecurityManager
,那么它会根据规则进行检查,有可能阻止 setAccessible(true)
。例如,某个 SecurityManager
可能不允许对 java
和 javax
开头的 package
的类调用 setAccessible(true)
,这样可以保证 JVM 核心库的安全。
总的来说就是用起来很麻烦,而且有配套的安全限制,麻烦 + 限制 ≈ 安全
IOC
什么是 IOC ?
IOC: Inversion of control 控制反转
IOC 所处的位置 ?
依赖倒转(原则) → 控制反转(思路) → 依赖注入(实现)
这种定位源于 IOC 相关知乎回答 ,个人其实不太认同这种定位,因为其实上面有一个没法自洽的地方,依赖倒转实际上是高层模块不应该依赖低层模块,二者都应该依赖其抽象(即引入接口层,模块 → 接口 ← 实现类),如果按照前面的依赖关系来看的话,模块 → 接口 → 实现类 变成了 模块 ← 接口 ← 服务类 的所体现依赖关系是有误的。但是既然都到反射这一层了,为什么还要纠结于类与接口呢?在 Class 层面来看众生平等( Class
是 class
的 class),依赖关系无非就是 class 之间的依赖罢了,索性把接口理解为中间人,按照后面的方式去理解,新秩序下新理解。既然不自洽源于承认 class 之间的差异性,在不需要承认 class 的差异的情况下,自洽本身的必要性便也就不复存在了。这里只是对于依赖倒转(原则) → 控制反转(思路)的不自洽做出了自己的诠释,其实个人感觉干脆不把依赖倒转当作 IOC 的思想源头也可以
IOC 的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。所以说,依赖反转并不能直接带来解耦合,解耦合的关键在于引入第三方管理,这进一步加强了个人对于上述定位的反对
依赖倒转原则
设计理念:相对于细节的多变性,抽象的东西要稳定的多
整体由部分构成这一客观现实是不可变的,但是谁主体谁让步是可以选的
本质上还是谁主体谁让步的问题,基于上述的设计理念得出的结果是:整体应该承担起主体的责任
控制反转思路
以下为个人理解
定义正向依赖为整体由部分集合构成,部分是主体,整体将实现的责任让步给部分,部分决定整体
则反向依赖相当于整体指导部分如何构成,整体是主体,整体将实现的责任留给自己,整体决定部分
如果还不能理解的话,就相当于自发组队形成队伍(正向依赖)和招聘队员形成队伍(反向依赖)
队伍都是由成员构成的,但是发动主体不一样
依赖注入
本质上是一个栈
相当于整体先验地拥有了构成其的类,再通过依赖注入获得的类组成主体
有点像网购,先下订单再配送
找了一篇比较好的文章,下面根据这篇文章进行进一步地学习
文中描述的控制反转与依赖注入的关系,与上述 IOC 所处的位置中的观点是一致的
在项目中我们通过使用「依赖注入」这种技术手段实现功能模块对其依赖组件的「控制反转」
为什么要引入 IOC ?
案例说明
将 User 中的数据存入不同的数据库中
现状:传统实现方法,模块和实现类都依赖于接口,要换数据库,就要在代码级别换 Service,这是不够灵活的
有没有这样一种解决方案,让我们的模块仅依赖于接口类,然后在项目运行阶段动态的插入具体的实现类,而非在编译(或编码)阶段将实现类接入到使用场景中呢?
这种动态接入的能力称为「插件」。
答案是有的:可以使用「控制反转」
「控制反转」提供了将「插件」组合进模块的能力。
在实现「控制反转」过程中我们「反转」了哪方面的「控制」呢?其实这里的「反转」的意义就是 如何去定位「插件」的具体实现。
采用「控制反转」模式时,我们通过一个组装模块,将「插件」的具体实现「注入」到模块中就可以了。
由一个独立的组装模块(容器)完成对实现类的实例化工作,这个组装模块就是「依赖注入容器」。
「依赖注入容器」是一个知道如何去实例化和配置依赖组件的对象。容器来承担项目运行阶段动态的插入具体的实现类的任务
再引入 配置 机制,就能实现脱离代码层,利用 priorities 来换数据库了,这就灵活得多了,回顾上学期的数据库实验和 Web 开发实验,事实上 JDBC 就是这么一个思路,先前感觉莫名其妙的突兀的配置机制现在突然就显得亲切而自然起来
总结一下,就是为了达成 解耦合 这一目标,首先引入控制反转思路(按照个人理解,简单理解成整体指导部分思想、插件思想也大差不差),实现 模块 与 实现类 在理念层面的解耦合(事实上,引入前后代码没有变化,只是思想从 模块 → 接口 → 实现类 变成了 模块 ← 接口 ← 服务类,看似简单,体现的却是主动权的变化(部分构成整体→整体指导部分),而主动权的变化使得后面单独分离出依赖注入容器实现解耦合成为可能),然后引入依赖注入实现,让容器来承担项目运行阶段动态的插入具体的实现类的任务,实现 模块 与 实现类 在代码层面的解耦合,最后引入配置机制,让 模块 与 实现类 实现脱离代码层的解耦合