第6章 Spring IoC容器之扩展篇

本章内容

  • Spring 2.5的基于注解的依赖注入
  • Spring 3.0展望

Spring 2.5 的基于注解的依赖注入

Spring2.5提供的 基于注解的依赖注入 功能延续了Spring框架内在IoC容器设计与实现上的一致性。除了依赖关系的“表达”方式上的不同,底层的实现机制基本上保持一致。

如果我们已经从Spring的IoC容器的XML之旅中成功走过来,那么在体验基于注解的依赖注入的过程中,一定会发现许多似曾相识的身影。你瞧,基于XML配置方式的自动绑定功能,就是我们再次邂逅的第一位老朋友…

注解版的自动绑定( @Autowired )

1. 从自动绑定(autowire)到@Autowired

在使用依赖注入绑定FXNews相关实现类时,为了减少配置量,我们可以采用Spring的IoC容器提供的自动绑定功能,如下所示:

 
    
    <beans default-autowire="byType">
    	<bean id="newsProvider" class="..FXNewsProvider" autowire="byType"/>
      <bean id="djNewsListener" class="..DowJonesNewsListener"/>
    	<bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
    </beans>

可以通过<beans>default-autowire来指定默认的自动绑定方式,也可以通过每个bean定义上的autowire来指定每个bean定义各自的自动绑定方式,它们都是触发容器对相应对象给予依赖注入的标志。

而将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于注解的自动绑定。

@Autowired是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些依赖。比如可以使用@Autowired对FXNewsProvider类进行标注,以表明要为FXNewsProvider注入的依赖。

下方代码给出了标注后的情况。

 
    
    public class FXNewsProvider {
      private IFXNewsListener newsListener;
      private IFXNewsPersister newPersistener;
      
      @Autowired
    	public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
    		this.newsListener = newsListner;
    		this.newPersistener = newsPersister;
    	}
    	...
    }
    

与原有的byType类型的自动绑定方式类似,@Autowired也是按照类型匹配进行依赖注入的,只不过,它要比byType更加灵活,也更加强大。@Autowired可以标注于类定义的多个位置,包括如下几个。

**域(Filed)或者说属性(Property)。**不管它们声明的访问限制符是privateprotected还是public,只要标注了@Autowired,它们所需要的依赖注入需求就都能够被满足,如下所示:

 
    
    public class FXNewsProvider {
    	@Autowired
    	private IFXNewsListener newsListener;
      @Autowired
    	private IFXNewsPersister newPersistener; ...
    }
    

**构造方法定义(Constructor)。**标注于类的构造方法之上的@Autowired,相当于抢夺了原有自动绑定功能中“constructor”方式的权利,它将根据构造方法参数类型,来决定将什么样的依赖对象注入给当前对象。从最初的代码示例中,我们可以看到标注于构造方法之上的@Autowired的用法。

方法定义(Method)。@Autowired不仅可以标注于传统的setter方法之上,而且还可以标注于任意名称的方法定义之上,只要该方法定义了需要被注入的参数。下方代码给出了一个标注于这种任意名称方法之上的@Autowired使用示例代码。

 
    
    public class FXNewsProvider {
        private IFXNewsListener newsListener;
        private IFXNewsPersister newPersistener;
    
        @Autowired
        public void setUp(IFXNewsListener newsListener, IFXNewsPersister newPersistener) {
            this.newsListener = newsListener;
            this.newPersistener = newPersistener;
        }
        ...
    }

现在,虽然可以随意地在类定义的各种合适的地方标注@Autowired,希望这些被@Autowired标注的依赖能够被注入,但是,仅将@Autowired标注于类定义中并不能让Spring的IoC容器聪明到自己去查看这些注解,然后注入符合条件的依赖对象。

容器需要某种方式来了解,哪些对象标注了@Autowired,哪些对象可以作为可供选择的依赖对象来注入给需要的对象。

在考虑使用什么方式实现这一功能之前,我们先比较一下原有的自动绑定功能与使用@Autowired之后产生了哪些差别。

使用自动绑定的时候,我们将所有对象相关的bean定义追加到了容器的配置文件中,然后使用default-autowire或者autowire告知容器,依照这两种属性指定的绑定方式,将容器中各个对象绑定到一起。在使用@Autowired之后,default-autowire或者autowire的职责就转给了@Autowired,所以,现在,容器的配置文件中就只剩下了一个个孤伶伶的bean定义,如下所示:

 
    <beans>
      <bean id="newsProvider" class="..FXNewsProvider"/>
      <bean id="djNewsListener" class="..DowJonesNewsListener"/>
      <bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
    </beans>
    

为了给容器中定义的每个bean定义对应的实例注入依赖,可以遍历它们,然后通过反射,检查每个bean定义对应的类上各种可能位置上的@Autowired。如果存在的话,就可以从当前容器管理的对象中获取符合条件的对象,设置给@Autowired所标注的属性域、构造方法或者方法定义。

整个逻辑如下方代码中的原型代码所示。

 
    
    Object[] beans = ...;
    for(Object bean : beans)
    {
      if (autowiredExistsOnField(bean)) {
        Field f = getQulifiedField(bean));
        setAccessiableIfNecessary(f);
        f.set(getBeanByTypeFromContainer());
      }
    
      if (autowiredExistsOnConstructor(bean)) {
        ...
      }
    
      if (autowiredExistsOnMethod(bean)) {
        ...
      }
    }
    

看到以上的原型代码所要完成的功能以及我们的设想,你一定想到了,我们可以提供一个Spring的IoC容器使用的BeanPostProcessor自定义实现,让这个BeanPostProcessor在实例化bean定义的过程中,来检查当前对象是否有@Autowired标注的依赖需要注入。

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor就是Spring提供的用于这一目的的BeanPostProcessor实现。所以,很幸运,我们不用自己去实现它了。

将FXNews相关类定义使用@Autowired标注之后,只要在IoC容器的配置文件中追加AutowiredAnnotationBeanPostProcessor就可以让整个应用开始运作了,如下所示:

 
    <beans>
    	<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    	<bean id="newsProvider" class="..FXNewsProvider"/>
    	<bean id="djNewsListener" class="..DowJonesNewsListener"/>
      <bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
    </beans>

重点在代码第2行。

当然,这需要我们使用ApplicationContext类型的容器,否则还得做点儿多余的准备工作。

看着依赖注入相关的信息,一半分散在Java源代码中(@Autowired标注的信息),一半依然留在XML配置文件里,你心里一定觉得很不爽。实际上,我也是,这不是折腾人吗?不过,别急,让我们先解决眼前的另一个问题,稍后再回过头来看看怎么进一步统一这两片国土。

2. @Qualifier 的陪伴

@Autowired是按照类型进行匹配,如果当前@Autowired标注的依赖在容器中只能找到一个实例与之对应的话,那还好。可是,要是能够同时找到两个或者多个同一类型的对象实例,又该怎么办呢?我们自己当然知道应该把具体哪个实例注入给当前对象,可是,IoC容器并不知道,所以,得通过某种方式告诉它。这时,就可以使用@Qualifier对依赖注入的条件做进一步限定,使得容器不再迷茫。

@Qualifier实际上是byName自动绑定的注解版,既然IoC容器无法自己从多个同一类型的实例中选取我们真正想要的那个,那么我们不妨就使用@Qualifier直接点名要哪个好了。假设FXNewsProvider使用的IFXNewsListener有两个实现,一个是DowJonesNewsListener,一个是ReutersNewsListener,二者相关配置如下:

 
    
    <beans>
    	<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    	<bean id="newsProvider" class="..FXNewsProvider"/>
      
      <!--重点是下面两行-->
    	<bean id="djNewsListener" class="..DowJonesNewsListener"/>
      <bean id="reutersNewsListner" class="..ReutersNewsListener"/>
      
      <bean id="djNewsPersister" class="..DowJonesNewsPersister"/>
    </beans>
    

如果我们想让FXNewsProvider使用ReutersNewsListener,那么就可以在FXNewsProvider的类定义中使用@Qualifier指定这一选择结果,如下:

 
    
    public class FXNewsProvider {
    	@Autowired
    	@Qualifier("reutersNewsListner")
    	private IFXNewsListener newsListener;
      @Autowired
    	private IFXNewsPersister newPersistener;
      ...
    }
    

以上我们使用的是标注于属性域的@Autowired进行依赖注入。如果使用@Autowired来标注构造方法或者方法定义的话,同样可以使用@Qualifier标注方法参数来达到限定注入实例的目的。代码清单6-4给出的正是标注于方法参数之上的@Qualifier的使用示例。

 
    
    
    public class FXNewsProvider {
    	...
    	@Autowired
    	public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) {
    		this.newsListener = newsListener; this.newPersistener = newPersistener;
      }
      ...
    }