IoC (Inverse of Control,控制反转)是 Spring 容器的底层核心功能。
1 概念
在电影《陆犯焉识》中,有这样一个场景:获得自由回归社会后的陆焉识,常常想起劳改时候的生活,对现在的社会许多现象感到愤怒与不解,小女儿于是嘲讽他 “ 那你为什么要回来? ” 陆焉识(扮演者是陈道明)却毫不在意地回答: “ 我是为了你妈妈! ” 这一句自信的回答,让小女儿羞愧地无地自容又羡慕地无以复加,她小半生业绩卓著,却从未遇到一个男人,这样坚定地为她!
这里我们通过 Java 语言来编写剧本,借此来理解 IoC 的概念。
public class Script {
/**
* 回答
*/
public void dialog() {
ChenDaoMing cdm = new ChenDaoMing();//扮演者侵入剧本
cdm.answer(" 我是为了你妈妈!");
}
}
我们会发现以上剧本,把作为具体饰演者的陈道明直接侵入到剧本中,使剧本和演员直接耦合在了一起:
剧本和演员直接耦合一个明智的编剧在剧情创作时应该围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地选择适合的演员,而非绑定在某一个人身上。所以,我们要为主人公陆焉识定义一个接口:
//引入 ”陆焉识“ 接口
LuYanShi luYanShi=new ChenDaoMing();
luYanShi.answer(" 我是为了你妈妈!");
剧本的情节通过角色展开,在拍摄时由演员饰演:
引入接口后的关系我们看到 Script 同时依赖于 LuYanShi 接口和 ChenDaoMing 类,并没有达到我们所期望的剧本仅依赖于角色的目的 。
可以在影片投拍时,由导演将 ChenDaoMing 类安排在 LuYanShi 的角色上,通过导演之手将剧本、角色、饰演者组合起来。
解耦剧本与演员导演就像是一台装配器,将具体角色的演员赋给了剧本中的某个角色。
现在我们可以讲讲 IoC 咯,IoC(Inverse of Control)的字面意思是控制反转,它包含:其一是“控制”,其二是“反转”。
对应到前面的例子,“控制”是指剧本角色的选择权,“反转”是指这种选择权从《陆犯焉识》的剧本中移除,转移到导演手中。对于软件而言,即是某一接口具体实现类的选择控制权从调用类中移除,转交给第三方来决定,即由 Spring 容器的 Bean 配置来决定。
因为 IoC 表达的不够直接,因此业界曾进行过广泛的讨论,最终由软件界的泰斗级人物 Martin Fowler 提出了 DI (依赖注入: Dependency Injection )的概念,即让调用类对某一接口的实现类的依赖关系由第三方(容器或协作类)注入,从而移除了类对某一接口实现类的依赖 。“ 依赖注入 ” 的概念显然比 “ 控制反转 ” 易于理解 。
2 类型
IoC I划分为三种注入类型,分别是构造函数注入、属性注入和接口注入。
2.1 构造函数注入
通过调用类的构造函数,将接口实现类通过构造函数的参数传入:
private LuYanShi luYanShi;
/**
* 注入陆焉识的演员
* @param luYanShi
*/
public Script(LuYanShi luYanShi) {
this.luYanShi = luYanShi;
}
/**
* 对话
*/
public void dialog3() {
luYanShi.answer(" 我是为了你妈妈!");
}
在导演类中,通过构造函数注入扮演陆焉识的演员:
public class Director {
public void direct(){
LuYanShi luYanShi=new ChenDaoMing();//指定演员
Script script=new Script(luYanShi);//注入
}
}
2.2 属性注入
有时,导演会发现,虽然陆焉识是影片的男主角,但并非每场戏都需要他的出场,所以通过构造函数方式注入并不恰当,这种情况下,可以使用属性注入方式 。 属性注入通过 setter 方法完成调用类所需依赖的注入,更加灵活方便:
private LuYanShi luYanShi;
/**
* 属性注入
* @param luYanShi
*/
public void setLuYanShi(LuYanShi luYanShi) {
this.luYanShi = luYanShi;
}
/**
* 对话
*/
public void dialog4() {
luYanShi.answer(" 我是为了你妈妈!");
}
导演类通过设置属性来注入具体的演员:
public void direct(){
LuYanShi luYanShi=new ChenDaoMing();//指定演员
Script script=new Script();
script.setLuYanShi(luYanShi);//属性注入
script.dialog4();
}
2.3 接口注入
将调用类中所有的注入方法抽象到一个接口中,调用类通过实现这一接口提供相应的注入方法。为了采取接口注入的方式,需要声明一个接口:
public interface ActorArrangable {
/**
* 接口注入
* @param luYanShi
*/
void setLuYanShi(LuYanShi luYanShi);
}
在剧本类中通过接口方法注入陆焉识的扮演者:
public class Script implements ActorArrangable{
private LuYanShi luYanShi;
/**
* 接口注入
* @param luYanShi
*/
public void setLuYanShi(LuYanShi luYanShi) {
this.luYanShi = luYanShi;
}
}
由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,所以不建议使用这种方式。
3 使用容器注入依赖关系
虽然剧本和陈道明实现了解耦,但这些工作在代码中依然存在,只是转移到导演手中而已,导致导演的权力非常大,不断出现潜规则。假设某一制片人想改变这一局面,在选好某一个剧本后,通过“海选”或者第三方机构来选择导演、演员,让他们各司其职,那么剧本、导演与演员就都实现了解耦咯。
所谓媒体“海选”和第三方机构在程序领域中,就是一个第三方容器,它帮助我们完成类的初始化和装配工作。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 实例化类-->
<bean id="luYanShi" class="net.deniro.springBoot.spring4.IoC.ChenDaoMing"/>
<!-- 建立依赖关系-->
<bean id="script" class="net.deniro.springBoot.spring4.IoC.Script"
p:luYanShi-ref="luYanShi"/>
</beans>
网友评论