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

IoC (Inverse of Control,控制反转)是 Spring 容器的底层核心功能。

1 概念

在电影《陆犯焉识》中,有这样一个场景:获得自由回归社会后的陆焉识,常常想起劳改时候的生活,对现在的社会许多现象感到愤怒与不解,小女儿于是嘲讽他 “ 那你为什么要回来? ” 陆焉识(扮演者是陈道明)却毫不在意地回答: “ 我是为了你妈妈! ” 这一句自信的回答,让小女儿羞愧地无地自容又羡慕地无以复加,她小半生业绩卓著,却从未遇到一个男人,这样坚定地为她!

这里我们通过 Java 语言来编写剧本,借此来理解 IoC 的概念。

public class Script {

    /**
     * 回答
     */
    public void dialog() {
        ChenDaoMing cdm = new ChenDaoMing();//扮演者侵入剧本
        cdm.answer(" 我是为了你妈妈!");
    }

}

我们会发现以上剧本,把作为具体饰演者的陈道明直接侵入到剧本中,使剧本和演员直接耦合在了一起:

真正理解 Spring 中的 IoC(控制反转)_第1张图片
剧本和演员直接耦合

一个明智的编剧在剧情创作时应该围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地选择适合的演员,而非绑定在某一个人身上。所以,我们要为主人公陆焉识定义一个接口:

//引入 ”陆焉识“ 接口
LuYanShi luYanShi=new ChenDaoMing();
luYanShi.answer(" 我是为了你妈妈!");

剧本的情节通过角色展开,在拍摄时由演员饰演:

真正理解 Spring 中的 IoC(控制反转)_第2张图片
引入接口后的关系

我们看到 Script 同时依赖于 LuYanShi 接口和 ChenDaoMing 类,并没有达到我们所期望的剧本仅依赖于角色的目的 。

可以在影片投拍时,由导演将 ChenDaoMing 类安排在 LuYanShi 的角色上,通过导演之手将剧本、角色、饰演者组合起来。

真正理解 Spring 中的 IoC(控制反转)_第3张图片
解耦剧本与演员

导演就像是一台装配器,将具体角色的演员赋给了剧本中的某个角色。

现在我们可以讲讲 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 使用容器注入依赖关系

虽然剧本和陈道明实现了解耦,但这些工作在代码中依然存在,只是转移到导演手中而已,导致导演的权力非常大,不断出现潜规则。假设某一制片人想改变这一局面,在选好某一个剧本后,通过“海选”或者第三方机构来选择导演、演员,让他们各司其职,那么剧本、导演与演员就都实现了解耦咯。

所谓媒体“海选”和第三方机构在程序领域中,就是一个第三方容器,它帮助我们完成类的初始化和装配工作。




    
    

    
    


你可能感兴趣的:(真正理解 Spring 中的 IoC(控制反转))