虽然业务对象可以通过IoC方式声明相应的依赖,但是最终仍然需要通过IoC Service Provider将这些相互依赖的对象绑定到一起。

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

   IFXNewsListener newsListener = new DowJonesNewsListener();
    IFXNewsPersister newsPersister = new DowJonesNewsPersister();
    FXNewsProvider newsProvider = new FXNewsProvider(newsListener,newsPersister);
    newsProvider.getAndPersistNews();

这段代码就可以认为是这个场景中的IoC Service Provider,只不过比较简单,而且目的也过于单一罢了。

IoC Service Provider的职责

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

  • 业务对象的构建管理 。IoC Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现。
  • 业务对象间的依赖绑定 。对于IoC Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointerException)。IoC Service Provider 通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定 ,从而保证每个业务对象在使用的时候,可以处于就绪状态。

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

IoC Service Provider需要记录对象之间的对应关系,当前流行的IoC Service Provider产品使用的注册对象管理信息的方式主要有以下几种。

直接编码方式

在容器启动之前,我们就可以 通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖注入关系 。下面的伪代码演示了这样一个过程。

    IoContainer container = ...;
    container.register(FXNewsProvider.class,new FXNewsProvider()); 
    container.register(IFXNewsListener.class,new DowJonesNewsListener());
    ...  
    FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class); 
    newProvider.getAndPersistNews();

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

如果是接口注入,可能伪代码看起来要多一些。不过,道理上是一样的,只不过除了注册相应对象,还要 将“注入标志接口”与相应的依赖对象绑定一下,才能让容器最终知道是一个什么样的对应关系,如下面代码所演示的那样。

    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方法将“被注入对象”(由IFXNewsListenerCallable接口添加标志) 所依赖的对象绑定为容器中注册过的IFXNewsListener类型的对象实例。容器在返回FXNewsProvider对象实例之前,会根据这个 绑定信息,将IFXNewsListener注册到容器中的对象实例注入到“被注入对象”——FXNewsProvider中,并最终返回已经组装完毕的FXNewsProvider对象。

所以,通过程序编码让最终的IoC Service Provider(也就是各个IoC框架或者容器实现)得以知晓服务的“奥义”,应该是管理依赖绑定关系 的最基本方式。

配置文件方式

这是一种较为普遍的依赖注入关系管理方式。像普通文本文件、properties文件、XML文件等,都可以成为管理依赖注入关系的载体。不过,最为常见的,还是通过XML文件来管理对象注册和对象间依赖关系

对于我们例子中的FXNewsProvider来说,也可以通过Spring配置文件的方式来配置和管理各个对象间的依赖关系。

    
    <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>

最后,我们就可以像下方代码所示的那样,通过“newsProvider”这个名字,从容器中取得已经组装好的FXNewsProvider并直接使用。

    ...
    container.readConfigurationFiles(...);
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider"); 
    newsProvider.getAndPersistNews();

注解方式

这种方式的代表实现是 Google Guice( 注:Spring 现在也是支持的 ),这是 Bob Lee 在 Java 5的注解和 Generic 的基础上开发的一套 IoC 框架。 我们可以直接在类中使用元数据信息来标注各个对象之间的依赖关系,然后由Guice框架根据这些注解所提供的信息将这些对象组装后,交给客户端对象使用。下面代码演示了使用Guice的相应注解标注后的FXNewsProvider定义。

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

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

    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那里 取得 最终已经注入完毕并直接可用的 对象 了。

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

当然,注解最终也要通过代码处理来确定最终的注入关系,从这点儿来说,注解方式可以算作 编码方式 的一种特殊情况。

小结

本章就IoC场景中的主要角色IoC Service Provider给出了介绍。讨论了IoC Service Provider的基本职责,以及它常用的几种依赖关系管理方式。

应该说,IoC Service Provider只是一个一般性的概念。下一章,我们将由一般到特殊,一起深入了解一个特定的IoC Service Provider实现产品, 即Spring提供的IoC容器