参照实例理解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语言本身的类反射功能。