为了更好地理解Spring的IoC容器,在这里我们通过具体的日常工作中分配工作的示例来模拟IOC的概念。
同时,Spring实现依赖注入的Java底层技术是 Java反射,因此我们也会对Java反射进行介绍。
Ioc (Inverse of Control 控制反转 )是 Spring容器的内核,AOP、声明式事务等功能都是以此为基础。
举个例子,日常工作(设计、开发、测试、集成等等)
小明直接处理开发工作:
================DoDistributedWork ==========================
package com.xgj.master.ioc;
import com.xgj.master.ioc.distributor.ProjectManager;
import com.xgj.master.ioc.intf.Dealer;
import com.xgj.master.ioc.specific.DealerImpl;
import com.xgj.master.ioc.specific.XiaoMing;
public class DoDistributedWork {
/**
*
* @Title: doDevelopWork
* @Description: 小明和具体的工作强耦合
* @return: void
*/
public void doDevelopWork(){
//小明直接侵入开发工作
XiaoMing xiaoMing = new XiaoMing();
xiaoMing.doWork();
}
......
}
=========================XiaoMing ==========================
package com.xgj.master.ioc.specific;
public class XiaoMing {
public void doWork() {
System.out.println(XiaoMing.class.getSimpleName() + " says that he will do the develop work");
}
}
让我们来看下此时的类关系图
可以发现: 开发工作和小明强耦合.
明智的管理者,会从工作分配的角度出发,而不会让工作和具体的处理人员强耦合的 ,那如何让小明和开发工作解耦呢?
同样的开发工作,小强小刚等都可以胜任,而不是绑定到小明一个人身上? 那该如何处理呢?
通过上述分析,我们知道需要为处理人员定义一个接口,任何实现了该接口的实现类都可以处理开发工作。
package com.xgj.master.ioc.intf;
public interface Dealer {
/**
*
* @Title: doDevelopWork
* @Description: 接口方法
* @return: void
*/
public void doDevelopWork();
}
接下来,只要继承该接口,就可以胜任开发工作
package com.xgj.master.ioc.specific;
import com.xgj.master.ioc.intf.Dealer;
public class DealerImpl implements Dealer{
@Override
public void doDevelopWork() {
System.out.println(DealerImpl.class.getSimpleName() +" says that he will do the work");
}
}
分配工作:
/**
*
* @Title: doDevelopWork2
* @Description: 引入接口,只奥是使具体的执行者和开发工作解耦
* @return: void
*
*/
public void doDevelopWork2(){
// 引入接口 Dealer
Dealer dealer = new DealerImpl();
// 通过接口处理对应的工作
dealer.doDevelopWork();
}
此时的UML:
此时我们发现:
DoDistrubuteWork 同时依赖 Dealer 和 DealerImpl,需要在DoDistrubuteWork 创建DealerImpl,并没有实现 工作只依赖Dealer的效果。
但是Dealer必须通过具体的实现类才能完成工作,如何让DealerImpl 和 DoDistrubuteWork 无关 同时又能完成Dealer 的具体动作呢?
我们引入 PM,UML关系图如下
/**
*
* @Title: pmDistributeWork
* @Description: 引入PM,使 工作和具体的执行者解耦
* @return: void
*/
public void pmDistributeWork(){
// 引入PM
ProjectManager projectManager = new ProjectManager();
// PM分配工作
projectManager.distributeWork();
}
通过引入PM,使得 工作和 具体的处理人员解耦。PM就像一台装配器,安排具体人员处理具体的工作。
现在我们反过来理解IOC。字面意思:控制反转
结合上面的例子:
- 控制: 选择具体Dealer的控制权
- 反转:指的是这种控制权转移到PM手中。
对于软件来说,即某一接口具体实现类的选择控制权从调用类中移除,转交由第三方决定, 即由Spring容器借由Bean配置来进行控制。
关于IoC的另外一个叫法,Martin Fowler提出了DI(Dependecy Injection 依赖注入),即让调用类对你一个接口实现类的依赖关系由地方(容器或者协作类)注入,以移除调用类对某一个接口实现类的依赖。
很显然, 依赖注入比控制反转更加直接明了,易于理解。
从注入方法上看, IoC分为
Spring支持 构造函数注入和属性注入。
在构造函数注入中,通过调用类的构造函数,将接口实现类通过构造函数变量传入
package com.xgj.master.ioc.consInj;
import com.xgj.master.ioc.specific.DealerImpl;
public class DoDistributedWork {
private DealerImpl dealerImpl ;
// 注入Dealer的实现类
public DoDistributedWork(DealerImpl dealerImpl){
this.dealerImpl = dealerImpl;
}
public void doSomething(){
dealerImpl.doDevelopWork();
}
}
DoDistributedWork 的构造函数不关心由谁来处理工作,只要在构造函数中传入的处理者能够完成指定工作即可, 具体的处理者由PM来安排,如下
package com.xgj.master.ioc.consInj;
import com.xgj.master.ioc.specific.DealerImpl;
public class PM {
public void distribute() {
// 指定Dealer的具体人员
DealerImpl dealerImpl = new DealerImpl();
// 注入dealerImpl到工作中
DoDistributedWork distributedWork = new DoDistributedWork(dealerImpl);
distributedWork.doSomething();
}
public static void main(String[] args) {
PM pm = new PM();
pm.distribute();
}
}
有时候,并非每个场景都需要DealerImpl,在这种情况使用构造函数注入并不妥当 ,可以考虑使用属性注入。
package com.xgj.master.ioc.properInj;
import com.xgj.master.ioc.intf.Dealer;
public class DoDistributedWork {
private Dealer dealer ;
// 属性注入
public void setDealer(Dealer dealer) {
this.dealer = dealer;
}
public void doSomething(){
dealer.doDevelopWork();
}
}
为Dealer提供一个setter方法,以便PM在需要注入Dealer的具体实现类。
package com.xgj.master.ioc.properInj;
import com.xgj.master.ioc.intf.Dealer;
import com.xgj.master.ioc.specific.DealerImpl;
public class PM {
public void distribute(){
Dealer dealer = new DealerImpl();
DoDistributedWork distributedWork = new DoDistributedWork();
//通过属性setter方法 注入
distributedWork.setDealer(dealer);
distributedWork.doSomething();
}
public static void main(String[] args) {
PM pm = new PM();
pm.distribute();
}
}
虽然实现了 DoDistributedWork 和 DealerImpl的解耦,但是这些代码仍然存在,只是转移到了PM中而已。
如何将PM这部分也不要呢? 假设有个管理部门,管理部分来选择PM、Dealer等等,那么 每个部分之间就都实现了解耦。我们可以更加专注于也位于逻辑的开发。
Spring就是这样的一个容器,通过配置文件或者注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。
package com.xgj.master.ioc.springInj;
import com.xgj.master.ioc.intf.Dealer;
public class DealerImpl implements Dealer{
@Override
public void doDevelopWork() {
System.out.println(DealerImpl.class.getSimpleName() +" says that he will do the work");
}
}
package com.xgj.master.ioc.springInj;
public class DoDistributedWork {
private DealerImpl dealerImpl ;
public void setDealerImpl(DealerImpl dealerImpl) {
this.dealerImpl = dealerImpl;
}
public void doSomething(){
dealerImpl.doDevelopWork();
}
}
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="dealerImpl" class="com.xgj.master.ioc.springInj.DealerImpl" />
<bean id="doDistributedWork" class="com.xgj.master.ioc.springInj.DoDistributedWork"
p:dealerImpl-ref="dealerImpl" />
beans>
测试类
package com.xgj.master.ioc.springInj;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringInjTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
DoDistributedWork doDistributedWork = (DoDistributedWork) context.getBean("doDistributedWork");
doDistributedWork.doSomething();
}
}
Spring为什么会这么简洁,仅仅靠一个配置文件就可以实例化并装配好程序用到的Bean呢?
主要归功于Java类反射功能。
详见 Java-Java反射