美文网首页Java 杂谈Java学习笔记我爱编程
真正理解 Spring 中的 IoC(控制反转)

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

作者: deniro | 来源:发表于2018-04-10 17:25 被阅读131次

    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>
    

    相关文章

      网友评论

      本文标题:真正理解 Spring 中的 IoC(控制反转)

      本文链接:https://www.haomeiwen.com/subject/barihftx.html