找回密码
 立即注册

QQ登录

只需一步,快速开始

WantSong

高级会员

65

主题

78

帖子

1113

积分

高级会员

积分
1113

活字格认证

WantSong
高级会员   /  发表于:2009-12-14 10:27  /   查看:5973  /  回复:0
——关于IOC的简单介绍


为什么会有IOC容器?  在Java阵营中,我们使用IOC & AOP 容器或者O/R Mapping这些轻量级的框架是为了摆脱对EJB容器的依赖。除去对分布式事务的支持,EJB容器提供了两个核心功能:组件生命周期管理和基础架构管理。
  EJB对组件生命周期管理,是指EJB技术定义了一组可重用的组件:Enterprise Beans,你可以利用这些组件象搭积木一样组成你的(分布式)系统。你用代码把这些组件写好,然后用配置文件组织起来。这些组件组成的系统就可以在装了EJB容器的平台上运行。所有的组件实例都运行在EJB容器中。容器提供了系统级的服务,并且控制EJB的生命周期。
  们使用IOC容器正是为了管理组件生命周期,也就是指对装载、初始化和销毁组件的管理。
  渐渐的,从Java阵营的流行风也吹到了.NET。
  
 为什么要使用IOC容器?  我的看法是,为了将组件的调用声明和使用分开。即使从最近更加火热的SOA来看,也与IOC同出一辙。我们看看SOA的目标:
·
业务功能虚拟化,通过将服务的定义和使用与服务的实现分离;
·
服务被松散绑定,并且可以通过强调位置透明性和互操作性的通信协议进行调用;
·
服务封装了可重用的业务功能。
  这里服务都是以组件的方式实现的。
  关于这个,Java的教父Martin Fowler已经解释很多很好。我们可以参考这篇《IoC 容器和Dependency Injection 模式》。

 如何实现定义使用与实现分离?  我们先看看下面这个实例:
//service
public Interface ISaleManager{

Account openAccount
(int ID);
}


public class SaleImpl implement ISaleManager{

public Account openAccount
(int ID){

... //do sth


}

}

//client
public ConcreteInvoke1{

public getAccount{


...


ISaleManager sm = new SaleImpl();


accout = sm.openAccount(
1);

...


}

}
  这样的调用不是一种好的做法。client部分这种实现方式不仅依赖接口,而且依赖接口的某种具体实现类,如果只是自己项目用问题还不是很大,但是如果别的team也希望用这个组件,但是他们有另外一种提供accout的方法,那么应该怎么办呢。我们可以依靠IOC容器为我们生成ISaleManager具体实例。
  IOC容器为我们提供了三种可行的方式:
·
接口注入(Interface Injection
·
设值方法注入(Setter Injection
·
构造子注入(Constructor Injection
  我们将在下面阐述这三种方式。

Type1,接口注入  我们还是先看例子:
//service
public Interface ISaleManager{

Account openAccount
(int ID);
}


public class SaleImpl implement ISaleManager{

public Account openAccount
(int ID){

... //do sth


}

}
//configure file
<component name="Sales" class="SaleImpl">
</component>

//client
public ConcreteInvoke1{

public getAccount{


...


ISaleManager sm = (ISaleManager) DIContainer.getComponent("Sales");


accout = sm.openAccount(
1);

...


}

}
  我们用配置文件将调用与实现在编译期分离,运行时调用者对容器传入调用标志,容器根据配置中的类名动态加载实现类。
  当然我们还有别的办法来实现类似效果,如抽象工厂或者Service Locator方式。
  然而这不算是IOC容器的核心,网上文章称“接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限”。


Type2,设值方法注入  我们对Client部分进行改进:
//client
public ConcreteInvoke1{

private ISaleManager sm;


public getAccount{


...


accout = sm.openAccount(
1);

...


}


public void setSaleManager(ISaleManager sm){


this.sm = sm;


}

}
  这时我们已经用到了容器对组件的管理,当我们从容器中取得ConcreteInvoke1的实例时,其属性sm被赋予配置文件中指定的SaleImpl类。如果SaleImpl用到了持久化类,我们也可以用注入的方式,而不关心究竟是用了哪个持久类。
public class SaleImpl implement ISaleManager{

private AccountPO acctPO;


public Account openAccount
(int ID){

...


po.getByID(
ID);

...


}

}
//configure file
<component name="SaleAccount" class="AccountPO1">
</component>
  

同样的,如果别的组件用到当前业务组件,也可以把对实例的管理交给容器。比如:
//other business component
public class RefundmentImpl implement IRefundment{

private
ISaleManager sales;

public Account
getDefaultAccount(int ID){

...


sales.openAccount(ID);


...


}

}
//configure file
<component name="Refundment" class="RefundmentImpl">
<initMethod name="getDefaultAccount">
<arg>1</arg>
</initMethod>
</component>

在上例中我们设置getDefaultAccount的默认参数为1。当然,如果将ID换作其他需要继续处理的组件也可以。在下例中,我们从容器中取得RefundmentImpl后,ISaleManager也就通过配置文件指定了。
//other business component
public class RefundmentImpl implement IRefundment{

private int ID;


public Account
getDefaultAccount(ISaleManager sales){

...


sales.openAccount(ID);


...


}

}
//configure file
<component name="Sales" class="SaleImpl">
</component>
<component name="Refundment" class="RefundmentImpl">
<initMethod name="getDefaultAccount">
<arg>Sales</arg>
</initMethod>
</component>
  


Type3,构造子注入  是指在构造期即建一个完整、合法的象,与Type2有些类似。
//service
public Interface ISaleManager{

Account openAccount
(int ID);
}


public class SaleImpl implement ISaleManager{

public Account openAccount
(int ID){

... //do sth


}

}
//configure file
<component name="Sales" class="SaleImpl">
</component>

//client
public ConcreteInvoke1{

private ISaleManager sm;


public ConcreteInvoke1(ISaleManager sales){


sm = sales;


}


public getAccount{


...


accout = sm.openAccount(
1);

...


}

}
  这个方式对需要单例模式的组件生成很有好处。

  我觉着这三种方式的差别都不大,说白了就是要将new A()的过程交给配置和容器来自动完成。
实际应用  目前实现了IOC容器的技术有很多,在Java方面杰出的代表是:Spring和PicoContainer。我们在选用IOC容器时需要考察以下三点:
  • 配置的灵活性
  • 侵入性——组件对容器的依赖程度
  • 是否可以脱离容器存在

  在实际应用中我们可以这样用,更换某个控件时:
//component supplier
public Interface ICertainControl{

...


void show();

...

}

public Class ConcreteTreeView implement ICertainControl{

public void show(){


...


}

}

//component invoker
...
ICertainControl myTree = new
ConcreteTreeView();
myTree.show();
...
  上例伪码我们调用了一个控件,并把它显示出来。假如这时控件提供方在UI上增加了一个更眩的控件,我们想要调用,必须更改调用者的代码。也许更改代码、重新编译在我们眼里没有什么;然而如果这个控件是一个业务组件,调用方是某个服务器,这就也为着我们必须关掉正在运行的服务以重新部署,这对WEB应用来说是个不得不关注的问题。
  我们可以在这里应用IOC,通过修改配置以实现热部署,即调用者无需做任何修改:
//component invoker
...
ICertainControl myTree = (ICertainControl) DIContainer.getComponent("TreeView");
myTree.show();
...

//configure file
<component name="TreeView" class="ConcreteTreeView"/>

这样即使控件提供方增加任何效果,只要它满足定义(即接口),调用者都无需关心具体的服务由谁实现。我们做到了将服务的定义和使用与服务的实现分离。
  当然,你可以说我用配置文件,再由一个类来做定位器,利用反射能够达到同样效果。这个方法我们统称为ServiceLocator,其与IOC最大的区别是:
  • 对于IoC模式来说,对象赋值是由外部代码完成的使用者处于被动地位;
  • 而对于ServiceLocator来说,对象赋值是使用者主动请求完成的,它处于主动地位。
  对于二者的差异,我们可以参考《IoC 容器和Dependency Injection 模式》,上面说的很明白。

  IOC只有这点好处,“将new A()的过程交给配置和容器来自动完成”。但是如果将IOC与AOP联合起来,便成为一柄利器(由于时间,AOP不在这里做阐述了)。还是引用第一个小例子,如果在openAccount中需要引入事务,我们需要:
//service
public Interface ISaleManager{

Account openAccount
(int ID);
}


public class SaleImpl implement ISaleManager{

public Account openAccount
(int ID){

Connetion conn = DriverManager.getConnection(“XXX”); //get connection


conn.setAutoCommit(false); // means open transaction


doSth(conn);

catch(Exception e){

conn.rollback(); // roll back


return;

}


conn.commit; // commit


conn.close();

}


private doSth(Connection conn)throws Exception{


...


}

}
  而应用AOP后,显示的事务声明都可以交给容器去做:
//service
public class SaleImpl implement ISaleManager, IDAOLayer{

public Account openAccount
(int ID){

Connetion conn;


doSth(conn); // connection is not initialized

}


private doSth(Connection conn)throws Exception{


...


}

}
//configure
<component name="Refundment" class="RefundmentImpl">

<aspect pointcut="
doSth">transactionInterceptor</aspect>
</component>
<component name="transactionInterceptor"
class="MyTransactionInterceptor"/>

//we just need a extra interceptor
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyInterceptor implements MethodInterceptor{
public Object invoke(MethodInvocation methodInvocation) throws Throwable {



Connection conn =
DriverManager.getConnection(“XXX”); //get connection

try{


methodInvocation.setArguments(conn);


Object result = methodInvocation.proceed();



return result;


}
// deal with exception


finally{


...


}


}

}
  如果我们使用运行时动态代理的AOP技术,容器对SaleImpl实例化后,事务中断便会加载到这个实例上。AOP对事务的抽取大致会使用上面这样一个思路。这样的好处是,事务控制被我从业务代码中剥离出来;编写业务代码的人不用再考虑如何实现事务,由谁实现。
  类似的,诸如权限、日志等都可以考虑使用IOC &amp; AOP。

IOC的进阶——ESB?  ESB(企业服务总线)是IBM为了实现SOA架构提出的几个重要概念之一。他提出,服务交互的参与双方并不直接交互,而是通过ESB,ESB为这种服务虚拟化提供:
  • 位置和标志
    参与方不必知道其他参与方的位置和标志;
  • 交互协议
    参与方不必采用相同的通信协议或交互方式;
  • (交互)服务质量
    参与方声明QOS要求,包括性能、可靠性、消息的加/解密等等。
  如果我们对IOC进行,
  • 在字符串调用方式上进行封装;
  • 组件双方调用可以采用更多的形式传递数据,如XML,值对象,消息或者UML定义;
  • 提供多种形式的中断(interceptor),如性能优化的、完整性保证的,或者加/解密的。
  这些仅仅是我个人的想法,提供给大家参考。

0 个回复

您需要登录后才可以回帖 登录 | 立即注册
返回顶部