Java IOC
时间:2010-09-23 来源:luochaoboy
1.IOC的本质
Ioc(Inversion of Control)中文译名控制反转
IoC意味着将设计好的类交给系统去控制,而不是在类的内部控制。这就称为控制反转。IOC要解决的就是程序之间调用的一个问题。基于IOC原理, 可衍生出不同种类的模式与框架。这些模式与框架可提供一种新的机制管理业务对象及其依赖关系, 可以减少“粘合”代码, 使依赖外置化, 可以在统一的地方管理依赖, 从而有效地降低软件开发问题的复杂度、减小维护的代价。
2.不用IOC面临的问题
比如,现在准备做一个业务操作类service,其代码实现如下:
Interface IService{
go();
}
public Service implements IService{
public void go() {
...
}
}
实现Service实例化的几种方式及分析:
第一种方式
Service service = new Service();
这样个方式非常直观, 但是维护性差,不能保证目前设计的正确性,如果在以后的使用中才发现错误那么就要很费劲的修改源代码。
第二种
IService service = new Service();
这样的方式一定程序上解决了和具体实现类耦合问题,但是还是面临着新的问题:
如果实现这个接口的具体类的类名变了,怎么办?
比如:现在2.0版本,Service.java变成了Service2.java
还是必须修改源代码。能不能当具体实现类的类名变化时,也不用修改源代码?那就得用第三种方式
第三种
IService service = ServiceFactory.Create();
工厂模式, 在Ioc容器出现以前被广泛应用于各种项目中, 甚至有这样的说法, 没有工厂的项目就不是好项目, 虽然有点偏激但却有一定道理. 以ServiceFactory.Create()来推迟了类的实例化, 这样看起来无论声明和实例化都脱离了对具体类的依赖, 但具体的类终究还是要实例化。
方案一
直接return new Service(); 这效果和上一种方式没啥本质区别了, 到头来换了具体实现类还得修改代码.
方案二
读取配置文件中的信息, 用反射来实例化组件, 彻底的把具体实现类的信息从代码中剥离到了配置文件中, 更换具体实现类时仅需要修改配置文件. 接口终于完全的发挥出了他的威力, 但这种方式造成大量不统一的工厂产生, 而具体实现类之间常会有着各种不同的依赖关系, 使得工厂的复杂度大大增加, 实现与管理这些工厂又成了新的问题, Ioc容器就在这样的需求下出现了.
第四种
IService service= CommonService.Container.Resolve<IService>();
看起来和通过工厂获取实例差不多, 但 CommonService.Container是简易包装的一个Ioc容器,现在只需要知道从容器中来获取类的实例. Ioc容器是一个可复用的类管理工具, 可以很方便的引入到项目中, 通过向其中简单的注册需要被管理的类后, 便能管理类和他们之间的关系. 这样避免了大量的工厂出现, 更进一步的减少了代码量, 提高了项目的扩展维护性.
3.控制反转的实现
IoC可以用不同的方式来实现。其主要实现方式有两种:
<1>依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。EJB和Apache Avalon都使用这种方式。(现在用的不多了)
<2>依赖注入(Dependency Injection):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。后者是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(ConstructorInjection)。
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造子或者接口,使容器可以在初始化时组装对象的依赖关系。
与依赖查找方式相比,主要优势为:
<1>查找定位操作与应用代码完全无关。
<2>不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
<3>不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
所以不会用依赖查找。
4. 依赖注入
IOC并不能清晰地解释类与类之间依赖的解耦关系,软件界的泰斗级人物Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念,即将客户类对接口实现类的依赖关系由第三方(容器或协作类)注入,以移除客户类对具体接口实现类的依赖。
我们使用抽象接口来隔离使用者和具体实现之间的依赖关系,但是不管再怎么抽象,最终还是要创建具体实现类的实例,这种创建具体实现类的实例对象就会造成对于具体实现的依赖,为了消除这种创建依赖性,需要把依赖移出到程序的外部(比如配置文件)。
使用依赖注入后,这些类完全是基于抽象接口编写而成的,所以可以最大限度地适应需求的变化。
4.1 举例说明
现有一个服务接口,只有一个行为,
public interface Iprint {
public void go();
}
IprintSer类是接口Iprint的实现者
public interface Iprint {
public void go();
}
Test类想调用这个服务,最原始的方法:
public Consumer{
IprintSer iprint=new IprintSer();
iprint.go();
}
4.2 构造子注入
通过构造函数,将接口的具体实现类注入进来。代码如下
public class Custom {
private IprintSer service = null;
public Custom(IprintSer service) {
this.service=service;
// TODO Auto-generated constructor stub
}
public void doSomething() {
service.go();
}
}
调用过程如下
IprintSer service=new IprintSer();
Custom custom=new Custom(service);
custom.doSomething();
4.3 Setter方法注入
private IprintSer service = null;
public void SetCustom(IprintSer service)
{
this.service=service;
// TODO Auto-generated constructor stub
}
public void doSomething() {
service.go();
}
调用过程如下
IprintSer service=new IprintSer();
Custom custom=new Custom();
custom.SetCustom(service);
custom.doSomething();
4.4接口注入
接口注入是指将客户类所有注入的方法抽取到一个接口中,客户类通过实现这一接口提供注入的方法。为了采取接口注入的方式,需要声明一个额外的接口:
public interface Injectable{
void injectService(IService service);
}
Consumer类通过继承Injectable接口注入
public Consumer implements Injectable{
private IService service = null;
public injectService(IService service){
this.service=service;
}
public void doSomething() {
service.go();
}
}
调用过程如下:
Service service=new Service();
Consumer consumer=new Consumer();
consumer.injectService(service);
consumer.doSomething();
由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,因此我们不提倡这种方式。
4.5类与类之间依赖的管理
类与类之间的依赖关系实现了解耦,但是类的实例化和类与类之间的注入依然是存在的,只是这个工作被延后并且由另外的类来完成,我们把管理这些类的实例化和对类与类之间关系进行管理的模块称之为第三方容器。
这种容器帮助我们完成类的初始化和装配工作,让我们从这些底层的实现类实例化,依赖关系的装配中脱离出来,专注于更有意思的业务代码的编写工作,那确实是挺惬意的事情。Spring就是这样一个容器,它通过配置文件描述类之间的依赖关系,下面是Spring配置文件的对以上实例进行配置的样式代码:
<beans>
<bean id="myservice" class="com.Service"></bean>
<bean id="consumer" class="com.Consumer">
<property name="service"><ref bean="myservice"/></property>
</bean>
</beans>
通过new XmlBeanFactory(“beans.xml”)等方式即可启动容器,在容器启动时,Spring根据配置文件的描述信息,通过Java的反射机制自动实例化Bean并完成依赖关系的建立,从容器中即可返回准备就绪的Bean实例,以待后续的使用。