Spring 2.5提供的基于注解的依赖注入功能延续了Spring框架内在IoC容器设计与实现上的一致性。 除了依赖关系的“表达”方式上的不同,底层的实现机制基本上保持一致。如果我们已经从Spring的 IoC容器的XML之旅中成功走过来,那么在体验基于注解的依赖注入的过程中,一定会发现许多似曾相识的身影
注解版的自动绑定(@Autowired)
- 从自动绑定(autowire)到@Autowired
在使用依赖注入绑定FXNews相关实现类时,为了减少配置量,我们可以采用Spring的IoC容器提供的自动绑定功能,如下所示:
可以通过default-autowire来指定默认的自动绑定方式,也可以通过每个bean定义上的 autowire来指定每个bean定义各自的自动绑定方式,它们都是触发容器对相应对象给予依赖注入的标 志。而将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于 注解的自动绑定。
@Autowired是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些 依赖。比如可以使用@Autowired对FXNewsProvider类进行标注,以表明要为FXNewsProvider注入 的依赖。代码清单6-1给出了标注后的情况。
与原有的byType类型的自动绑定方式类似,@Autowired也是按照类型匹配进行依赖注入的,只 不过,它要比byType更加灵活,也更加强大。@Autowired可以标注于类定义的多个位置,包括如下 几个。
域(Filed)或者说属性(Property)。不管它们声明的访问限制符是private、protected还是 public,只要标注了@Autowired,它们所需要的依赖注入需求就都能够被满足,如下所示:
构造方法定义(Constructor)。标注于类的构造方法之上的@Autowired,相当于抢夺了原有自 动绑定功能中“constructor”方式的权利,它将根据构造方法参数类型,来决定将什么样的依赖对象 注入给当前对象。从最初的代码示例中,我们可以看到标注于构造方法之上的@Autowired的用法。
方法定义(Method)。@Autowired不仅可以标注于传统的setter方法之上,而且还可以标注于任 意名称的方法定义之上,只要该方法定义了需要被注入的参数。代码清单6-2给出了一个标注于这种任 意名称方法之上的@Autowired使用示例代码。
现在,虽然可以随意地在类定义的各种合适的地方标注@Autowired,希望这些被@Autowired标 注的依赖能够被注入,但是,仅将@Autowired标注于类定义中并不能让Spring的IoC容器聪明到自己 去查看这些注解,然后注入符合条件的依赖对象。容器需要某种方式来了解,哪些对象标注了 @Autowired,哪些对象可以作为可供选择的依赖对象来注入给需要的对象。在考虑使用什么方式实 现这一功能之前,我们先比较一下原有的自动绑定功能与使用@Autowired之后产生了哪些差别。
使用自动绑定的时候,我们将所有对象相关的bean定义追加到了容器的配置文件中,然后使用 default-autowire或者autowire告知容器,依照这两种属性指定的绑定方式,将容器中各个对象绑定到一起。在使用@Autowired之后,default-autowire或者autowire的职责就转给了@Autowired, 所以,现在,容器的配置文件中就只剩下了一个个孤伶伶的bean定义,如下所示:
为了给容器中定义的每个bean定义对应的实例注入依赖,可以遍历它们,然后通过反射,检查每 个bean定义对应的类上各种可能位置上的@Autowired。如果存在的话,就可以从当前容器管理的对象 中获取符合条件的对象,设置给@Autowired所标注的属性域、构造方法或者方法定义。整个逻辑如 代码清单6-3中的原型代码所示。
看到以上的原型代码所要完成的功能以及我们的设想,你一定想到了,我们可以提供一个Spring 的IoC容器使用的BeanPostProcessor自定义实现,让这个BeanPostProcessor在实例化bean定义的 过程中,来检查当前对象是否有@Autowired标注的依赖需要注入。org.springframework.beans. factory.annotation.AutowiredAnnotationBeanPostProcessor就是Spring提供的用于这一目的 的BeanPostProcessor实现。所以,很幸运,我们不用自己去实现它了。
将FXNews相关类定义使用@Autowired标注之后,只要在IoC容器的配置文件中追加Autowired- AnnotationBeanPostProcessor就可以让整个应用开始运作了,如下所示
当然,这需要我们使用ApplicationContext类型的容器,否则还得做点儿多余的准备工作。
- @Qualifier的陪伴
@Autowired是按照类型进行匹配,如果当前@Autowired标注的依赖在容器中只能找到一个实例与之对应的话,那还好。可是,要是能够同时找到两个或者多个同一类型的对象实例,又该怎么办呢? 我们自己当然知道应该把具体哪个实例注入给当前对象,可是,IoC容器并不知道,所以,得通过某 种方式告诉它。这时,就可以使用@Qualifier对依赖注入的条件做进一步限定,使得容器不再迷茫。
@Qualifier实际上是byName自动绑定的注解版,既然IoC容器无法自己从多个同一类型的实例中 选取我们真正想要的那个,那么我们不妨就使用@Qualifier直接点名要哪个好了。假设FXNews- Provider使用的IFXNewsListener有两个实现,一个是DowJonesNewsListener,一个是ReutersNe- wsListener,二者相关配置如下:
如果我们想让FXNewsProvider使用ReutersNewsListener,那么就可以在FXNewsProvider的类定 义中使用@Qualifier指定这一选择结果,如下:
以上我们使用的是标注于属性域的@Autowired进行依赖注入。如果使用@Autowired来标注构造 方法或者方法定义的话,同样可以使用@Qualifier标注方法参数来达到限定注入实例的目的。代码 清单6-4给出的正是标注于方法参数之上的@Qualifier的使用示例。
@Autowired之外的选择
Spring 2.5提供的基于注解的依赖注入,除了可以使用Spring提供的@Autowired和@Qualifier来 标注相应类定义之外,还可以使用JSR250的@Resource和@PostConstruct以及@PreDestroy对相应 类进行标注,这同样可以达到依赖注入的目的。
@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容 器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实 例注入给@Resource所标注的对象。同样的FXNewsProvider,如若使用@Resource进行标注以获取依 赖注入的话,类似如下的样子:
JSR250规定,如果@Resource标注于属性域或者方法之上的话,相应的容器将负责把指定的资源 注入给当前对象,所以,除了像我们这样直接在属性域上标注@Resource,还可以在构造方法或者普 通方法定义上标注@Resource,这与@Autowired能够存在的地方大致相同。
确切地说,@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生 命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的 init-method和destroy-method起到类似的作用。代码清单6-5给出了可能使用这两个注解的示例代 码。
如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个 方法清理某些资源,那么就可以像我们这样,使用@PostConstruct和@PreDestroy来标注这些方法。 当然,是使用@PostConstruct和@PreDestroy,还是使用Spring的InitializingBean和Disposable-Bean接口,或者init-method和destroy-method配置项,可以根据个人的喜好自己决定。
天上永远不会掉馅饼,我们只是使用@Resource或者@PostConstruct和@PreDestroy标注了相应 对象,并不能给该对象带来想要的东西。所以,就像@Autowired需要AutowiredAnnotationBean- PostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPost-
Processor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context. annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添 加到容器,JSR250的相关注解才能发挥作用,通常如下添加相关配置即可:
既然不管是@Autowired还是@Resource都需要添加相应的BeanPostProcessor到容器,那么我们 就可以在基于XSD的配置文件中使用一个<context:annotation-config>配置搞定以上所有的 BeanPostProcessor配置,如代码清单6-6所示。
<context:annotation-config> 不 但 帮 我 们 把 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPost- Processor和RequiredAnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!
Spring提供的@Autowired加上@Qualifier和JSR250提供的@Resource等注解属于两个 派系。如果要实现依赖注入的话,使用一个派别的注解就可以了。当然,既然<context:anno- tation-config>对两个派系都提供了BeanPostProcessor的支持,混合使用也是没有问题的, 只要别造成使用上的混乱就行。
将革命进行得更彻底一些(classpath-scanning 功能介绍)
好了,该来解决让我们不爽的那个问题了。到目前为止,我们还是需要将相应对象的bean定义,一个个地添加到IoC容器的配置文件中。与之前唯一的区别就是,不用在配置文件中明确指定依赖关 系了(改用注解来表达了嘛)。既然使用注解来表达对象之间的依赖注入关系,那为什么不搞的彻底 一点儿,将那些几乎“光秃秃”的bean定义从配置文件中彻底消灭呢?OK,我们想到了,Spring开发 团队也想到了,classpath-scanning的功能正是因此而诞生的!
使用相应的注解对组成应用程序的相关类进行标注之后,classpath-scanning功能可以从某一顶层 包(base package)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构 建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。这之后所发生的事情就不 用我说了,既然相关的类已经添加到了容器,那么后面BeanPostProcessor为@Autowired或者 @Resource所提供的注入肯定是有东西拿咯!
classpath-scanning功能的触发是由<context:component-scan>决定的。按照如下代码,在XSD 形式(也只能是XSD形式)的配置文件中添加该项配置之后,classpath-scanning功能立即开启:
现在<context:component-scan>将遍历扫描org.spring21路径下的所有类型定义,寻找标注了相应注解的类,并添加到IoC容器。
如果要扫描的类定义存在于不同的源码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。需要的话,不要犹豫!
<context:component-scan>默认扫描的注解类型是@Component。不过,在@Component语义基 础上细化后的@Repository、@Service和@Controller也同样可以获得<context:component-scan> 的青睐。@Component的语义更广、更宽泛,而@Repository、@Service和@Controller的语义则更具 体。所以,同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。 对于其他两种注解也是同样道理,我们暂且使用语义更广的@Component来标注FXNews相关类,以便 摆脱每次都要向IoC容器配置添加bean定义的苦恼。使用@Component标注后的FXNews相关类见代码清 单6-7。
<context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的 命名规则,来生成那些添加到容器的bean定义的名称(beanName)。比如DowJonesNewsPersister通 过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可 以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。
现在,除了<context:component-scan>是唯一需要添加到IoC容器的配置内容,所有的工作都 可以围绕着使用注解的Java源代码来完成了。如果现在加载配置文件,启动FXNewProvider来处理外 汇新闻的话,我们可以得到预期的运行效果,运行的代码如下所示:
你或许会觉得有些诧异,因为我们并没有使用<context:annotation-config>甚至直接将相应 的BeanPostProcessor添加到容器中,而FXNewsProvider怎么会获得相应的依赖注入呢?这个得怪 <context:component-scan>“多管闲事”,它同时将AutowiredAnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor一并注册到了容器中,所以,依赖注入的需求得以满足。如 果你不喜欢,非要自己通过<context:annotation-config>或者直接添加相关BeanPost- Processor的方式来满足@Autowired或者@Resource的需求,可以将<context:component-scan>的 annotation-config属性值从默认的true改为false。不过,我想没有太好的理由非要这么做吧?
小结
Spring最初并不支持基于注解的依赖注入方式。所以,在Spring 2.5中引入这一依赖注入方式的时 候,肯定要在维护整个框架设计与实现的一致性和引入这种依赖注入方式对整个框架的冲击之间做出 权衡。最终的结果我们已经看到了,Spring 2.5中引入的基于注解的依赖注入从整体上保持了框架内的 一致性,同时又提供了足够的基于注解的依赖注入表达能力。我想,最初的决定和最终的效果都是令 人满意的。虽然我们还会部分地依赖于容器的配置文件,但通过20%的工作却可以带来80%的效果, 这本身已经是最好的结果了。
不过,从实际开发角度看,如果非要使用完全基于注解的依赖注入的话,或许会遇到一些过不去 的坎儿。比如,对于第三方提供的类库,肯定没法给其中的相关类标注@Component之类的注解。这 时,我们可以结合使用基于配置文件的依赖注入方式。毕竟,基于XML的依赖注入方式是Spring提供 的最基本、也最为强大的表达方式了!