IoC概述
IoC(Inverse of Controller,控制反转)是spring容器的基础,AOP以及声明式事务等功能都以此为基础。对于刚开始接触Spring的程序员来说,IoC这个重要的概念比较晦涩难懂,在此通过一个例子说明这个概念。
1. 通过实例理解IoC概念
以《墨攻》电影为例,其中有一个场景,当刘德华所饰演的墨者革离到达梁国都城下时,城上梁国守军问道:“来者何人?”刘德华回答:“墨者革离!”我们不妨通过Java语言为这个“城门叩问”的场景编写剧本,并借此理解IoC的概念,代码如下所示。
/**
* 通过使用演员编排剧本
*/
public class MoAttack {
public void cityGateAsk() {
// 演员直接侵入剧本
LiuDeHua ldh = new LiuDeHua();
ldh.responseAsk("墨者革离!");
}
}
通过代码我们会发现作为具体角色饰演者的刘德华直接侵入剧本,使剧本和演员直接耦合在一起。一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才能在剧本投拍时自由的选择任何合适的演员,而非绑定在某一个人身上。通过以上分析,我们需要为该剧本的主人公革离定义一个接口,接口如下所示。
/**
* 通过使用角色编排剧本
*/
public class MoAttack {
public void cityGateAsk() {
// 引入革离角色接口
GeLi geli = new LiuDeHua();
// 通过接口展开剧情
geli.responseAsk("墨者革离!");
}
}
增加GeLi角色后,该剧本的情节通过角色展开,在拍摄时角色由演员饰演。但是MoAttack却同时依赖于GeLi接口和LiuDeHua类,并没有达到我们所期盼的剧本仅依赖于角色的目的。如何让LiuDeHua和剧本无关而又完成GeLi的具体动作呢?我们可以通过导演负责剧本,角色,饰演者三者的协调。通过引入导演,使得剧本和具体饰演者解耦。
现在,让我们看看IoC的概念,IoC的字面意思是控制反转,概念包含两方面:
1. 控制:选择GeLi角色扮演者的控制权
2. 反转:控制权从《墨攻》剧本中移除,转交给导演的手中
对于软件而言,某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置进行控制。因为IoC确实不够开门见山,因此业界曾进行广泛的讨论,最终软件界的泰斗级任务Martin Fowler提出了DI(Dependency Injection,依赖注入)概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方注入,以移除调用类对某一接口实现类的依赖。
2. IoC类型
从注入方法上来看,IoC主要可以划分为三种类型:构造函数注入,属性注入和接口注入。Spring支持构造注入以及属性注入。下面继续使用以上的例子说明三种注入方式的区别。
2.1 构造函数注入
通过调用类的构造函数,将接口实现类通过构造函数变量传入,代码如下所示。
public class MoAttack {
private GeLi geli;
// 注入革离具体饰演者
public MoAttack(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者革离!");
}
}
MoAttack的构造函数不关心具体由谁来饰演革离这个角色,只要传入的饰演者按剧本要求完成相应的表演即可,角色的具体饰演者有导演来安排,代码如下所示。
public class Director {
public void direct() {
// 指定角色的饰演者
GeLi geli = new LiuDeHua();
// 注入具体饰演者到剧本中
MoAttack moAttack = new MoAttack(geli);
moAttack.cityGateAsk();
}
}
导演首先安排刘德华饰演革离,并将刘德华“注入”到《墨攻》剧本中,然后进行“城门叩问”剧情的演出工作。
2.2 属性注入
属性注入可以有选择的通过setter方法完成调用类所需依赖的注入,更加灵活方便,代码如下所示。
public class MoAttack {
private GeLi geli;
// 属性注入方法
public void setGeLi(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者革离!");
}
}
代码中为geli属性提供了一个Setter方法,以便让导演在需要时注入geli的具体饰演者,如下代码所示。
public class Director {
public void direct() {
MoAttack moAttack = new MoAttack();
// 调用属性Setter方法注入
GeLi geli = new LiuDeHua();
moAttack.setGeli(geli);
moAttack.cityGateAsk();
}
}
和通过构造函数注入革离饰演者不同,在实例化MoAttack剧本时,并未指定任何饰演者,而是实例化MoAttack后,在需要革离出场时,才调用setGeli()方法注入饰演者。
2.3 接口注入
将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。为了采取接口注入的方式,必须先声明一个ActorArrangable接口,接口如下:
public interface ActorArrangable {
void injectGeli()
}
MoAttack通过该接口提供具体的实现,代码如下。
public class MoAttack implements ActorArrangable{
private GeLi geli;
// 实现接口方法
public void injectGeli(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者革离!");
}
}
Director通过ActorArrangable的injectGeli()方法完成饰演者的注入工作,代码如下。
public class Director {
public void direct() {
MoAttack moAttack = new MoAttack();
GeLi geli = new LiuDeHua();
moAttack.injectGeli(geli);
moAttack.cityGateAsk();
}
}
由于通过接口注入需要额外声明一个接口,增加了类的数目,而且效果和属性注入并无本质区别,并不推荐这种注入方式。
3. 通过容器完成依赖属性关系的注入
虽然MoAttack和LiuDeHua实现了解耦,MoAttack无须关注角色实现类的实例化工作,但这些工作中在代码中依然存在,只是转移到Director类中。现在,通过一个第三方的容器,帮助完成类的初始化与装配工作,让开发者从这些底层实现类的实例化,依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发工作。
Spring就是这样的一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。当容器启动时,Spring根据配置文件的描述信息,自动实例化Bean并完成依赖关系的装配,从容器中即可返回准备就绪的Bean实例,后续可直接使用。