真正理解 Spring 中的 IoC(控制反转)

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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容