java技术基础程序员

java基础 IoC介绍及其简单实现

2017-04-03  本文已影响222人  步积

IoC(Inverse of Control 控制反转)是Spring容器的内核,AOP和声明式事务等功能都是基于此技术实现。

参照实例理解IoC

参考网址中的刘德华饰演墨者革离的例子,能帮助我们更好的理解IoC的原理,因此此处我们依然使用这个例子进行IoC的学习。

代码1:通过演员安排剧本

public class MoAttack {
    public void cityGateAsk() {
        //演员直接侵入剧本
        LiuDeHua liuDeHua = new LiuDeHua();
        liuDeHua.responseAsk("Who are you?");
    }
}
剧本和演员直接耦合

这里可以看出,演员同剧本耦合度太高,如果演员临时出现什么状况,可能就会对整部电影有影响。因此聪明的编剧,在创作时应围绕剧情和故事,而不是某一个演员,这样在能在投入拍摄的时候自由遴选演员,而非只能绑定在刘德华的身上。因此此处我们应该真对角色革离设定一个接口。

代码2:引入革离角色

public class MoAttack {
    public void cityGateAsk() {
        //引入革离角色接口IGeLi
        IGeLi iGeLi = new LiuDeHua();
        
        //调用角色行为
        iGeLi.responseAsk("Who are you?");
    }
}

在此处引用了革离角色接口,剧情以角色展开,在拍摄时选择刘德华进行拍摄,进行的也是角色行为。此时墨攻、革离、刘德华的关系就如下图

引入革离角色接口后的关系

此时MoAttach同时依赖于IGeLi接口和LiuDeHua类,并没有达到真正的剧本只依赖于角色的目的。但是实际剧本拍摄中,又离不开演员,因此如何能让LiuDeHua与剧本MoAttach无关,而又能完成角色IGeLi的具体动作呢?当然是具体拍摄时,导演分配LiuDeHua饰演IGeLi角色,导演将MoAttach剧本、IGeLi角色、饰演者联系在一起。

引入导演后,剧本同饰演者解耦

通过引入导演,实现了剧本同具体表演者解耦。对应到软件设计中,导演就是一个装配器,安排演员表演具体的角色。
据此我们可以反过来讲解IoC的概念了。IoC字面意思为控制反转,包括两个内容:控制反转。上述的例子中,控制指的是角色革离扮演者的控制权,而反转是将控制权从剧本中移除,转交到导演的手里。

将某一接口具体实现类的控制权从调用类中移除,转交给第三方决定。在Spring中,Spring框架就是第三方。

IoC的类型

从注入方法上划分,可以主要分为三种类型:构造函数注入、属性注入、接口注入。Spring支持构造函数注入和属性输入。下面我们继续以上面的例子来讲述三种实现方法的区别。

构造函数注入

在构造函数注入中,通过调用类的构造函数,将接口的具体实现类通过构造函数变量传入。如下述代码所示

代码3.1 通过构造函数注入革离实际扮演者

public class MoAttack {
    private IGeLi geLi;
    //通过构造函数注入革离的实际扮演者
    public MoAttack(IGeLi iGeLi){
        this.geLi = iGeLi;
    }

    public void cityGateAsk() {
        this.geLi.responseAsk("Who are you?");
    }
}

此处的MoAttack不关心革离的具体扮演者是谁,只需要这个扮演者按照剧本表演革离的动作即可。而革离的具体表演者由导演来安排。

代码3.2 导演通过剧本的构造函数注入实际表演者

public class Director {
    private String userName = "FengXiaoGang";
    public void directMovie() {
        //声明革离的实际扮演者
        IGeLi geLi = new LiuDeHua();

        //通过MoAttack的构造函数注入革离的实际扮演者
        MoAttack moAttack = new MoAttack(geLi);
        moAttack.cityGateAsk();
    }
}

属性注入

在剧本中,革离虽然是第一主角,但是并非在每个场景都会出现。使用构造函数注入的隔离角色,会在每个场景都出现,这也是不恰当的。这时就可以考虑使用属性注入,属性注入可以通过对象的setter方法,灵活的在需要的时候进行调用类所需依赖的注入。

代码3.3 通过setter方法注入实际扮演者

public class MoAttack {
    private IGeLi geLi;
    //MoAttack类提供setter方法,用于在导演在需要的时候注入革离的实际扮演者
    public void setGeLi(IGeLi geLi) {
        this.geLi = geLi;
    }

    public void cityGateAsk() {
        this.geLi.responseAsk("Who are you?");
    }
}

代码3.4 导演注入革离实际扮演者

public class Director {
    private String userName = "FengXiaoGang";
    public void directMovie() {
        //声明革离实际扮演者
        IGeLi geLi = new LiuDeHua();
        MoAttack moAttack = new MoAttack();
        //调用setter方法注入革离实际扮演者
        moAttack.setGeLi(geLi);
        moAttack.cityGateAsk();
    }
}

在剧本开始,没有革离出场的时候,不生成革离的实际扮演者,等到需要的时候,使用setter方法注入革离的实际扮演者,即可使用。由此可以在需要其他角色出场时,提供其他角色的setter方法,导演在出场时调用setter方法注入实际扮演者即可。

接口注入

将调用类中需要注入的方法提取到接口中,调用类通过实现接口提供的注入方法。为了实现接口注入的形式,应当先生成一个接口方法:

代码3.5 声明接口方法

public interface IActorArrange {
    //声明革离角色的注入方法
    void injectGeLi(IGeLi geLi);
}

代码3.6 实现接口内的注入方法

public class MoAttack implements IActorArrange {
    private IGeLi geLi;

    public void injectGeLi(IGeLi geLi) {
        this.geLi = geLi;
    }

    public void cityGateAsk() {
        this.geLi.responseAsk("Who are you?");
    }
}

代码3.7 导演调用接口方法注入革离实际扮演者

public class Director {
    private String userName = "FengXiaoGang";
    public void directMovie() {
        IGeLi geLi = new LiuDeHua();
        MoAttack moAttack = new MoAttack();

        moAttack.injectGeLi(geLi);
        moAttack.cityGateAsk();
    }
}

通过接口注入方法需要额外声明一个接口,增加类的树木,且其实现效果与属性注入没有本质上的区别,因此不提倡采用此方法。

通过容器完成依赖关系的注入

上述代码最终虽然将剧本和实际扮演者解耦,剧本类无需关注角色扮演类的实例化工作,但是这些代码依然存在,只是转移到了导演类中实例化而已。如果制片人想要在改变这一现状,将角色扮演者的筛选工作交给第三方,这样就真正实现了导演、剧本、角色、实际扮演者的真正解耦。
所谓第三方在程序中就是一个第三方的容器,它帮助完成类的初始化和实例工作,让开发者从这些底层实现类的实例化、依赖关系装配等工作中脱离出来,专注于更有意义的业务逻辑开发工作。Spring提供的就是这样的容器,它通过配置文件或注解描述类和类的依赖关系,自动完成类的初始化和实例工作。

代码 配置文件片段

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="geli" class="com.sschen.ioclearning.LiuDeHua"></bean>
    <bean id="moAttack" class="com.sschen.ioclearning.MoAttack">
        <property name="geLi" ref="geli"></property>
    </bean>
</beans>

在导演类中获取配置节点中内容,调用角色进行表演

代码 导演类获取配置

public class Director {
    private String userName = "FengXiaoGang";
    public void directMovie() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Bean.xml");
        MoAttack moAttack = applicationContext.getBean("moAttack", MoAttack.class);

        moAttack.cityGateAsk();
    }
}

如此就真正实现了同角色实际扮演者的解耦,将这部分工作交给Spring配置来实现。在容器启动时,Spring根据配置文件的描述信息,自动实例化Bean并完成依赖关系的装配,从容器中即可返回准备就绪的Bean实例,后续可直接使用。
那么为什么Spring能够通过仅仅在配置文件中配置的bean节点,就能实例化并且装配好程序所用的bean呢?这就应改归功于Java语言本身的类反射功能。

参考:
[Java]Spring Ioc讲解,不怕你不懂

上一篇下一篇

猜你喜欢

热点阅读