IoC容器和IoC Service Provider

IoC的基本概念

Ioc的作用简单说就是创建对象由以前的程序员自己new构造方法来调用,变成了交由Spring创建对象。举个例子,出门之前得先穿件外套吧?以前,你得自己跑到衣柜面前取出衣服这一依赖对象,然后自己穿上再出门。而现在,你只要跟你的“另一半”使个眼色或者一句话,她就会心领神会地到衣柜那里为你取出衣服,然后再给你穿上,现在你就可以出门了。

通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider(例子中你的另一半)来打交道,素有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中。

依赖注入的方式

IoC有三种依赖注入的方式,即构造方法注入、setter方法注入以及接口注入

构造方法注入

构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是Ioc容器)知道它需要哪些依赖对象,例子如下:

1
2
3
4
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister{  
this.newsListener = newsListner;
this.newPersistener = newsPersister;
}

IoC Service Provider会检查被注入对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。同一个对象是不可能被构造两次的,因此,被注入对象的构造乃至整个生命周期,应该是由IoC Service Provider来管理的。

构造方法注入方式比较直观,对象被构造完,即进入就绪状态,可以马上使用。

setter方法注入

对应Java Bean对象来说,通过setter方法,可以更改相应的对象属性;通过getter方法,可以获得相应属性的状态。所以当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相对应的依赖对象设置到被注入对象中。。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FXNewsProvider {  

private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;

public IFXNewsListener getNewsListener() {
return newsListener;
}
public void setNewsListener(IFXNewsListener newsListener) {
this.newsListener = newsListener;
}
public IFXNewsPersister getNewPersistener() {
return newPersistener;
}
public void setNewPersistener(IFXNewsPersister newPersistener) {
this.newPersistener = newPersistener;
}
}

这样外界就可以通过调用setNewsListener和setNewPersistener方法为FXNewsProvider对象注入所依赖的对象了。

setter方法注入相对更宽松一些,可以在对象构造完成后在诸如。

接口注入

相对于前两种方式来说,接口注入就没有那么简单明了了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。

如上图,FXNewsProvider为了让IoC Service Provider为其注入所依赖的IFXNewsListener,首先需要实现IFXNewsListenerCallable接口,这个接口会声明一个injectNewsListner方法(方法名随意),该方法的参数,就是所依赖对象的类型。这样,InjectionServiceContainer对象,即对应IoC Service Provider就可以通过这个接口方法将依赖对象注入到被注入对象FXNewsProvider中。

相对于前两种依赖注入方式,接口注入比较死板和繁琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口。

三种注入方式比较

  • 接口注入:从使用上来说,接口注入是现在不提倡的一种方式,因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此。
  • 构造方法注入:这种注入方法的优点就是,对象在构造完后,即进入就绪状态,可以马上使用。**缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java方法中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数列表的变动可能造成维护上的不便。**
  • setter方法注入:因为方法可以命名,所以setter方法注入在描述上要比构造方法注入好一些。另外setter方法可以被继承,允许设置默认值,而且有良好IDE支持。缺点就是对象无法再构造完成马上进入就绪状态。

IoC Service Provider简介

Ioc Service Provider是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式。它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或者IoC容器。

Ioc Service Provider的职责

IoC Service Provider的职责相对来说比较简单,主要有两个:业务对象的构建管理和业务对象间的依赖绑定。

  • 业务对象的构建管理:在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但这部分工作始终需要有人来做,所以IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
  • 业务对象间的绑定:Ioc Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

IoC Service Provider如何管理对象间的依赖关系

介绍归纳当前流行的IoC Service Provider产品使用的注册对象管理信息的方式主要有以下几种。

直接编码方式

当前大部分的IoC容器都应该支持直接编码方式,包括Spring。在容器启动之前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖注入关系。下面代码演示了这样的一个过程:

1
2
3
4
5
6
7
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...

FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();

通过为相应的类指定对应的具体实例,可以告知IoC容器,当我们要这种类型的对象实例时,请将容器中注册的、对应的那个具体实例返回给我们。

如果是接口注入方式,可能伪代码要多一些,但是本质的道理上是一样的。

1
2
3
4
5
6
7
8
9
10
IoContainer container = ...;
container.register(FXNewsProvider.class,new FXNewsProvider());
container.register(IFXNewsListener.class,new DowJonesNewsListener());
...

container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class));
...

FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();

通过bind方法将“被注入对象”所依赖的对象,绑定为容器中注册过的IFXNewsListener类型的对象实例。

配置文件方式

这是一种较为普遍的依赖注入关系管理方式,像properties文件、XML文件等,都可以成为管理依赖注入关系的载体,其中最为常见的就是XML文件的方式。代码例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>

<bean id="djNewsListener" class="..impl.DowJonesNewsListener"> </bean>

<bean id="djNewsPersister" class="..impl.DowJonesNewsPersister"> </bean>

最后,在代码中通过“newProvider”这个名字,即可从容器中取得已经组装好的FXNewsProvider并直接使用。

1
2
3
4
... 
container.readConfigurationFiles(...);
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
newsProvider.getAndPersistNews();

元数据方式

这种方式使用Google Guice实现的一种注解方式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class FXNewsProvider {  
private IFXNewsListener newsListener;
private IFXNewsPersister newPersistener;

@Inject 5
public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister){
this.newsListener = listener;
this.newPersistener = persister;
}

...
}

通过@Inject,我们指明需要IoC Service Provider通过构造方法注入方式,为FXNewsProvider注入其所依赖的对象。至于余下的依赖信息,在Guice中是由相应的Module来提供的。下面是FXNewsProvider所使用的Module实现:

1
2
3
4
5
6
7
8
9
public class NewsBindingModule extends AbstractModule  {  
@Override
protected void configure() {
bind(IFXNewsListener.class) ➥
.to(DowJonesNewsListener.class).in(Scopes.SINGLETON);
bind(IFXNewsPersister.class) ➥
.to(DowJonesNewsPersister.class).in(Scopes.SINGLETON);
}
}

通过Module指定进一步的依赖注入相关信息之后,就可以直接从Guice那里取得最终已经注入完毕,并且直接可用的对象了。

1
2
3
Injector injector = Guice.createInjector(new NewsBindingModule()); 
FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
newsProvider.getAndPersistNews();