场景介绍
为了更好的说明Ioc注入方式,我们先想象一个场景:《喜剧之王》大家应该都看过,周星驰在里面饰演尹天仇,里面有一段戏可谓经典,周星驰扮演送外卖的卧底给黑社会人员送外卖被识破,无奈说了一句:“我真的只是个送外卖的!”,在程序里面,首先我们会想到这样实现:
public class Comedy{
public void action{
ZhouXingChi zxc = new ZhouXingChi();
zxc.say("我真的只是个送外卖的");
}
}
这里面其实有个问题,就是周星驰这个演员直接侵入了剧本(Comedy),使剧本和演员直接耦合在了一起,而正常的剧本肯定是围绕具体角色(尹天仇)而展开,在剧本投入拍摄时候选取最适合的演员才对,而非绑定到一个具体的人身上。改进后我们会想到使用接口实现,为剧本的主人公尹天仇定义一个接口,通过接口实现者引入演员:
public class Comedy{
public void action{
//通过接口引入尹天仇
YinTianChou ytc = new ZhouXingChi();
//通过接口展开剧情
ytc.say("我真的只是个送外卖的");
}
}
这样的好处就是,剧本的情节依赖于角色展开,在具体拍摄时角色由演员饰演。这样看似美好,其实剧本类还是依赖于YinTianChou接口和ZhouXingChi类,并未实现剧本和演员解耦。要想实现这个,我们还需要一个导演,导演的工作是将演员安排到角色上,导演负责剧本、角色和演员的协调工作。对应到我们的程序里,导演就像一台装配器,安排演员去饰演角色。
接下来以这个例子为引子,我们了解下Ioc:Ioc(Inverse of Control)的意思是控制反转,这包含两个含义,一方面是控制,其二是反转。那么什么东西的“控制”被“反转”了呢?回想刚才的例子,控制是指角色YinTianChou的饰演者的控制权,反转是指这种控制权从《喜剧之王》剧本中解除,交到导演手中,即某一接口具体实现类的选择权从调用类中移除,转交给第三方,即Spring容器通过Bean配置来进行控制,Ioc说是控制反转,其实不如说是依赖注入来的好理解。接下来说下如何实现注入:
Ioc注入方法
Ioc注入方法有三种:构造函数注入、属性注入和接口注入。其中接口注入不常用,我们重点说下前两种:
1.构造函数注入
以上述例子开始,通过调用类的构造函数,将接口实现类传入。
public class Comedy{
private YinTianChou ytc;
//通过构造函数注入具体饰演者
public Comedy(YinTianChou ytc){
this.ytc=ytc;
}
public void action{
ytc.say("我真的只是个送外卖的");
}
}
这样,Comedy的构造函数无须关心谁来饰演角色,即只要实现YinTianChou接口的实现类都可以,角色的饰演者由导演制定:
public class Director{
public void direct(){
//指定角色饰演者
YinTianChou ytc = new ZhouXingChi();
//注入饰演者到剧本
Comedy comedy = new Comedy(ytc);
comedy.action();
}
}
这样就实现剧本和演员解耦。但是考虑一个情况,就是虽然尹天仇是喜剧之王的主要角色,但是并非所有时间都需要尹天仇参演(不然配角、龙套怎么混饭吃),这时候通过构造函数将其注入到剧本就不合适了,我们需要用属性注入
2.属性注入
属性注入即通过setter方法完成调用类依赖的注入,更加灵活:
public class Comedy{
private YinTianChou ytc;
//通过属性注入具体饰演者
public void setYinTianChou(YinTianChou ytc){
this.ytc=ytc;
}
public void action{
ytc.say("我真的只是个送外卖的");
}
}
这样导演就可以在需要的时候注入YinTianChou的具体饰演者:
public class Director{
public void direct(){
//指定角色饰演者
YinTianChou ytc = new ZhouXingChi();
Comedy comedy = new Comedy();
//属性注入
comedy.setYinTianChou(ytc);
comedy.action();
}
}
这样在实例化Comedy时,没有指定任何饰演者,配角、龙套可以演戏,当需要尹天仇出场时,调用setert方法注入饰演者周星驰。但这样是不是很完美呢?
通过容器完成依赖注入
上述例子中虽然Comedy和ZhouXingChi实现了解耦,Comedy无须关注角色实现类的实例化,但是并不是不需要做这个工作,只是交给了导演类而已,为了改变这一局面,我们需要一种机制,在这个例子中我们可以举办一场“全民投票”,由媒体投票决定谁当导演、谁当演员,这样剧本、演员、导演都实现了解耦。这个“全民投票”或者说第三方代理机构在程序上我们称为第三方的容器,这个容器帮助我们完成类的初始化和装配,帮助我们实例化类并解决依赖关系。Spring就是这样一个容器,spring通过配置文件或注解描述类之间的依赖关系,自动完成类的初始化和依赖注入。
我们只需要在配置文件中加入:
<!— 实现类实例化—>
<bean id=“ytc" class=“ZhouXingChi”/>
<!— 通过geli-ref建立依赖关系—>
<bean id="Comedy" class =“com.xxx.Comedyk”
p:geli-ref="ytc”/>
至此,完美解决了上述问题,spring的强大也初露锋芒。至于spring为什么能实现这个功能,依赖的是java的反射功能。有兴趣的大家可以继续深入学习!
参考《Spring+4.x++企业应用开发实战》