Spring IOC的基本概念,IOC Service Provider,IOC容器扩展

目录

loC的基本概念

简介

如何注入对象

构造方法注入

setter方法注入

接口注入

三种注入方式的比较

loC的附加值

IoC Service Provider

简介

loC Service Provider的职责

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

直接编码方式

配置文件方式

元数据方式

IoC容器之扩展篇

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

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

@Autowired之外的选择            使用JSR250标注依赖注入关系

将革命进行得更彻底一些( classpath-scanning功能介绍)


loC的基本概念

简介

IoC是随着近年来轻量级容器(Lightweight Container)的兴起而逐渐被很多人提起的一个名词,它 的全称为Inversion of Control, 中文通常翻译为“控制反转",它还有一个别名叫做依赖注入(Dependency Injection)。好莱坞原则"Don't call us, we will call you." Q)恰如其分地表达了“反转"的意味,是用 来形容IoC最多的一句话。

注意 本章更多的是将IoC和依赖注入看作等同的概念进行讲解。但是,在这一点上可能存在 不同的观点,比如Expert Spring MVC and Web Flow和Expert One-on-One J2EE without EJB等书 中都将依赖注入看作是IoC的一种方式。可以暂且忽略这些观点,将IoC和依赖注入等同 看待。理解了依赖注入之后,可以再结合其他资料对IoC做进一步的研究。

为了更好地阐述IoC模式的概念,我们引入以下简单场景。

在我经历的FX项目@中,经常需要近乎实时地为客户提供外汇新闻。通常情况下,都是先从不同 的新闻社订阅新闻来源,然后通过批处理程序定时地到指定的新闻服务器抓取最新的外汇新闻,接着 将这些新闻存入本地数据库,最后在FX系统的前台界面显示。

假设我们有一个FXNewsProvider类来做以上工作,其代码如代码清单

其中,FNewsProvider需要依赖IFXNewsLis七ener来帮助抓取新闻内容,并依赖IFXNewsPersister存储抓取的新闻。

假设默认使用道琼斯(Dow Jones)新闻社的新闻,那么我们相应地提供了DowJonesNewsListener和DowJonesNewsPersister两两个实现。通常情况下,需要在构造函数中构造IFXNewsProvider 依赖的这两个类(以下将这种被其他类依赖的类或对象,简称为“依赖类”、 “依赖对象")'如代 码清单

看,这就是我们通常的做事方式!如果我们依赖于某个类或服务,最简单而有效的方式就是直接 在类的构造函数中新建相应的依赖类。这就好比要装修新房,需要用家具,这个时候,根据通常解决 对象依赖关系的做法,我们就会直接打造出需要的家具来。不过,通常都是分工明确的,所以,大多 数情况下,我们可以去家具广场将家具买回来,然后根据需要装修布置即可。

不管是直接打造家具(通过new构造对象),还是去家具广场买家具(或许是通过Service­Locator解决直接的依赖耦合),有一个共同点需要我们关注,那就是,我们都是自己主动地去获 取依赖的对象!

可是回头想想,我们自己每次用到什么依赖对象都要主动地去获取,这是否真的必要?我们最终 所要做的,其实就是直接调用依赖对象所提供的某项服务而已。只要用到这个依赖对象的时候,它能 够准备就绪,我们完全可以不管这个对象是自己找来的还是别人送过来的。对于FXNewsProvider来 说,那就是在getAndPersistNews()方法调用newsListener的相应方法时,newsListener能够准 备就绪就可以了。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要大费周折地自己去 折腾?

实际上,IoC就是为了帮助我们避免之前的“大费周折”,而提供了更加轻松简洁的方式。它的 反转,就反转在让你从原来的事必躬亲,转变为现在的享受服务。你想啊,原来还得鞍马劳顿,什么东西都得自己去拿。现在是用什么,让别人直接送过来就成。所以,简单点儿说,IoC的理念就是, 让别人为你服务!在图中,也就是让IoC Service Provider来为你服务

通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理。被注入对象需要 什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而 达到IoC Service Provider为被注入对象服务的目的。

IoC Service Provider在这里就是通常的IoC容器所充 当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转, 控制也从被注入对象转到了IoC Service Provider那里.

其实IoC就这么简单!原来是需要什么东西自己去拿,现在是需要什么东西就让别人送过来。图 以两种场景,形象地说明了使用IoC模式前后的差别。

出门之前得先穿件外套吧?以前,你得自己跑到衣柜前面取出衣服这一依赖对象,然后自己穿上 再出门。而现在,你只要跟你的“另—半“使个眼色或说一旬"Honey, 衣服拿来。”她就会心领神 会地到衣柜那里为你取出衣服,然后再给你穿上。现在,你就可以出门了。 (此时此刻,你心里肯定 窃喜, “有人照顾的感觉真好! )对你来说,到底哪种场景比较恓意,我想已经不言自明了吧?

如何注入对象

"伙计,来杯啤酒!”当你来到酒吧,想要喝杯啤酒的时候,通常会直接招呼服务生,让他为你 送来一杯清凉解渴的啤酒。同样地,作为被注入对象,要想让IoC Service Provider为其提供服务,并 将所需要的被依赖对象送过来,也需要通过某种方式通知对方。

  • 如果你是酒吧的常客,或许你刚坐好,服务生已经将你最常喝的啤酒放到了你面前;
  • 如果你是初次或偶尔光顾,也许你坐下之后还要招呼服务生, "Waiter,Tsingdao, please." ;
  • 还有一种可能,你根本就不知道哪个牌子是哪个牌子,这时,你只能打手势或干脆画出商标 图来告诉服务生你到底想要什么了吧!

不管怎样,你终究会找到一种方式来向服务生表达你的需求,以便他为你提供适当的服务。那么, 在IoC模式中,被注入对象又是通过哪些方式来通知IoC Service Provider为其提供适当服务的呢?

IoC模式最权威的总结和解释,应该是Martin Fowler的那篇文章"Inversion of Control Containers and the Dependency Injection pattern" , 其中提到了三种依赖注入的方式,即构造方法注入(constructor 叨ection)、setter方法注入(setter injection)以及接口注入(interface injection)。下面让我们详细看 一下这三种方式的特点及其相互之间的差别。

构造方法注入

顾名思义,构造方法注入,就是被注入对象可以通过在其构造方法中声明依赖对象的参数列表, 让外部(通常是IoC容器)知道它需要哪些依赖对象。对于前面例子中的FXNewsProvider来说,只要 声明如下构造方法(见代码清单)即可支持构造方法注入。

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

构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。这就好比你 刚进酒吧的门,服务生已经将你喜欢的啤酒摆上了桌面一样。坐下就可马上享受一份清凉与恨意。

setter方法注入

对于JavaBean对象来说,通常会通过setXXX ()和getXXX()方法来访问对应属性。这些setXXX()方 法统称为setter方法,getXXX()当然就称为getter方法。通过setter方法,可以更改相应的对象属性,通 过getter方法,可以获得相应属性的状态。

所以,当前对象只要为其依赖对象所对应的属性添加setter 方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。以FXNewsProvider为例,添加 setter方法后如代码清单所示。

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

setter方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些, 可以在对象构造完成后再注入。这就好比你可以到酒吧坐下后再决定要点什么啤酒,可以要百威,也 可以要大雪,随意性比较强。如果你不急着喝,这种方式当然是最适合你的。

接口注入

相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。 IoC Service Provider最终通过这些接口来了解应该为被注入对象注入什么依赖对象。图演示了如何 使用接口注入为FXNewsProvider注入依赖对象。

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

小心 在这种情况下,实现的接口和接口中声明的方法名称都不重要。重要的是接口中声明方法的参数类型,必须是”被注入对象”所依赖对象的类型。

接口注入方式最早并且使用最多的是在一个叫做Avalon的项目中,相对于前两种依赖注入方式, 接口注入比较死板和烦琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口。这就 好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒杯式的帽子,看起来有点多此—举。

三种注入方式的比较

接口注入。从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,基本处于“退 役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter 方法注入则不需要如此。

构造方法注入。这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以 马上使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反 射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且 在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多 个构造方法,而参数数量的变动可能造成维护上的不便。

setter方法注入。因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好一些。

另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无 法在构造完成后马上进入就绪状态。

综上所述,构造方法注入和setter方法注入因为其侵入性较弱,且易于理解和使用,所以是现在使 用最多的注入方式;而接口注入因为侵入性较强,近年来已经不流行了。

loC的附加值

从主动获取依赖关系的方式转向IoC方式,不只是一个方向上的改变,简单的转变背后实际上蕴 藏着更多的玄机。要说IoC模式能带给我们什么好处,可能各种资料或书籍中已经罗列很多了。比如 不会对业务对象构成很强的侵入性,使用IoC后,对象具有更好的可测试性、可重用性和可扩展性, 等等。不过,泛泛而谈可能无法真正地让你深刻理解IoC模式带来的诸多好处,所以,还是让我们从 具体的示例入手,来一探究竟吧。

对于前面例子中的FXNewsProvider来说,在使用IoC重构之前,如果没有其他需求或变动,不光 看起来,用起来也是没有问题的。但是,当系统中需要追加逻辑以处理另一家新闻社的新闻来源时, 问题就来了。

突然有一天,客户告诉你,我们又搞定一家新闻社,现在可以使用他们的新闻服务了,这家新闻 社叫MarketWin24。这个时候,你该如何处理呢?首先,毫无疑问地,应该先根据MarketWin24的服务 接口提供一个MarketWin24NewsListener实现,用来接收新闻;其次,因为都是相同的数据访问逻辑, 所以原来的DowJonesNewsPersister可以重用,我们先放在一边不管。最后,就主要是业务处理对象 FXNewsProvider了。因为我们之前没有用IoC, 所以,现在的对象跟DowJonesNewsListener是绑定 的,我们无法重用这个类了,不是吗?为了解决问题,我们可能要重新实现一个继承自 FXNewsProvider的MarketWin24NewsProvider, 或者于脆重新写一个类似的功能。

而使用IoC后,面对同样的需求,我们却完全可以不做任何改动,就直接使用FXNewsProvider。

因为不管是Dow Jones还是MarketWin24, 对于我们的系统来说,处理逻辑实际上应该是一样的:根据 各个公司的连接接口取得新闻,然后将取得的新闻存入数据库。因此,我们只要根据MarketWin24的 新闻服务接口,为MarketWin24的FXNewsProvider提供相应的MarketWin24NewsListener注入就可 以了,见代码清单。

看!使用IoC之后,FXNewsProvider可以重用,而不必因为添加新闻来源去重新实现新的 FXNewsProvider。实际上,只需要给出特定的FXNewsListener实现即可。

随着开源项目的成功,TDD (Test Driven Developement, 测试驱动开发)已经成为越来越受重视 的一种开发方式。因为保证业务对象拥有良好的可测试性,可以为最终交付高质量的软件奠定良好的 基础,同时也拉起了产品质量的第一道安全网。所以对于软件开发来说,设计开发可测试性良好的业 务对象是至关重要的。而IoC模式可以让我们更容易达到这个目的。比如,使用IoC模式后,为了测试 FXNewsProvider, 我们可以根据测试的需求,提供一个MockNewsListener给FXNewsProvider。在 此之前,我们无法将对DowJonesNewsListener的依赖排除在外,从而导致难以开展单元测试。而现 在,单元测试则可以毫无牵绊地进行,代码清单演示了测试取得新闻失败的情形。

如果要用一旬话来概括IoC可以带给我们什么,那么我希望是,IoC是一种可以帮助我们解耦各业 务对象间依赖关系的对象绑定方式((IOC是一种对象绑定方式,它的作用是解耦对象之间的依赖关系)

IoC Service Provider

简介

虽然业务对象可以通过IoC方式声明相应的依赖,但是最终仍然需要通过某种角色或者服务将这 些相互依赖的对象绑定到一起,而IoC Service Provider就对应IoC场景中的这一角色。

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

这段代码就可以认为是这个场景中的IoC Service Provider, 只不过比较简单,而且目的也过于单 一罢了。要将系统中几十、几百甚至数以于计的业务对象绑定到一起,采用这种方式显然是不切实际的。 通用性暂且不提,单单是写这些绑定代码也会是一种很糟糕的体验。不过,好在现在许多开源产品通过 各种方式为我们做了这部分工作。所以,目前来看,我们只需要使用这些产品提供的服务就可以了。Spring 的IoC容器就是—个提供依赖注入服务的IoC Service Provider。

loC Service Provider的职责

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

业务对象的构建管理。在IoC场景中,业务对象无需关心所依赖的对象如何构建如何取得,但 这部分工作始终需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对 象那里剥离出来,以免这部分逻辑污染业务对象的实现。

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

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

前面我们说过,被注入对象可以通过多种方式通知IoC Service Provider为其注入相应依赖。但问 题在于,收到通知的IoC Service Provider是否就一定能够完全领会被注入对象的意图,并及时有效地 为其提供想要的依赖呢?有些时候,事情可能并非像我们所想象的那样理所当然。

还是拿酒吧的例子说事儿,不管是常客还是初次光顾,你都可以点自己需要的饮料,以任何方式 通知服务生都可以。要是侍者经验老道,你需要的任何饮品他都知道如何为你调制并提供给你。可是, 如果服务生刚入行叉会如何呢?当他连啤酒、鸡尾酒都分不清的时候,你能指望他及时地将你需要的 饮品端上来吗?

服务生最终必须知道顾客点的饮品与库存饮品的对应关系,才能为顾客端上适当的饮品。对于为 被注入对象提供依赖注入的IoC Service Provider来说,它也同样需要知道自己所管理和掌握的被注入 对象和依赖对象之间的对应关系。

IoC Service Provider不是人类,也就不能像酒吧服务生那样通过大脑来记忆和存储所有的相关信 息。所以,它需要寻求其他方式来记录诸多对象之间的对应关系。

我们可以归纳一下,当前流行的IoC Service Provider产品使用的注册对象管理信息的方式主要有 以下儿种。

直接编码方式

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

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

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

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

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

配置文件方式

这是一种较为普遍的依赖注入关系管理方式。像普通文本文件、properties文件、XML文件等,都 可以成为管理依赖注入关系的载体。不过,最为常见的,还是通过XML文件来管理对象注册和对象间 依赖关系,比如Spring IoC容器和在和PicoContainer基础上扩展的NanoContainer, 都是采用XML文件来 管理和保存依赖注入信息的。对于我们例子中的FXNewsProvider来说,也可以通过Spring配置文件的 方式(见代码清单)来配置和管理各个对象间的依赖关系。

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

元数据方式

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

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

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

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


IoC容器之扩展篇

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

Spring 2.5提供的基于注解的依赖注入功能延续了Spring框架内在IoC容器设计与实现上的一致性。

除了依赖关系的”表达”方式上的不同,底层的实现机制基本上保待一致。如果我们已经从Spring的 IoC容器的XML之旅中成功走过来,那么在体验基于注解的依赖注入的过程中,一定会发现许多似曾 相识的身影。你瞧,基于XML配置方式的自动绑定功能,就是我们再次逆返的第一位老朋友……

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

从自动绑定(autowire)到@Autowired

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

可以通过default-autowire来指定默认的自动绑定方式,也可以通过每个bean定义上的 autowire来指定每个bean定义各自的自动绑定方式,它们都是触发容器对相应对象给予依赖注入的标 志。而将自动绑定的标志用注解来表示时,也就得到了基于注解的依赖注入,或者更确切地称为基于 注解的自动绑定。

@Autowired. 是基于注解的依赖注入的核心注解,它的存在可以让容器知道需要为当前类注入哪些 依赖。比如可以使用@Autowired. 对FXNevvsProvider类进行标注,以表明要为FXNevvsProvider注入 的依赖。代码清单给出了标注后的情况。

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

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

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

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

现在,虽然可以随意地在类定义的各种合适的地方标注@Autowired, 希望这些被@Autowired标 注的依赖能够被注入,但是,仅将@Autowired标注于类定义中并不能让Spring的IoC容器聪明到自己 去查看这些注解,然后注入符合条件的依赖对象。容器需要某种方式来了解,哪些对象标注了 @Autowired, 哪些对象可以作为可供选择的依赖对象来注入给需要的对象。在考虑使用什么方式实 现这一功能之前,我们先比较一下原有的自动绑定功能与使用@Autowired之后产生了哪些差别。

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

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

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

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

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

@Qualifier的陪伴

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

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

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

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

@Autowired之外的选择            使用JSR250标注依赖注入关系

Spring 2.5提供的基于注解的依赖注入,除了可以使用Spring提供的@Autowired和@Qualifier来 标注相应类定义之外,还可以使用JSR250的@Resource和@Post Construct以及@PreDestroy对相应 类进行标注,这同样可以达到依赖注入的目的。

@Resource与@Autowired不同,它遵循的是byName自动绑定形式的行为准则,也就是说,IoC容 器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,然后将查找到的对象实 例注入给@Resource所标注的对象。同样的FNewsProvider, 如若使用@Resource进行标注以获取依 赖注入的话,类似如下的样子:

JSR250规定,如果@Resource标注于属性域或者方法之上的话,相应的容器将负责把指定的资源 注入给当前对象,所以,除了像我们这样直接在属性域上标注@Resource, 还可以在构造方法或者普 通方法定义上标注@Resource, 这与@Autowired能够存在的地方大致相同。

确切地说,@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生 命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的 init-method和destroy-method起到类似的作用。代码清单给出了可能使用这两个注解的示例代 码。

如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个 方法清理某些资源,那么就可以像我们这样,使用@PostConstruct和@PreDestroy标注这些方法。 当然,是使用@PostConstruct和@PreDestroy, 还是使用Spring的InitializingBean和DisposableBean接口,或者init-method和destroy-method配置项,可以根据个人的喜好自己决定。

天上永远不会掉馅饼,我们只是使用@Resource或者@PostConstruct和@PreDestroy标注了相应 对象,并不能给该对象带来想要的东西。所以,就像@Autowired需要AutowiredAnnotationBean­PostProcessor为它与IoC容器牵线搭桥一样,JSR250的这些注解也同样需要一个BeanPostProcessor帮助它们实现自身的价值。这个BeanPostProcessor就是org.springframework.context. annotation.CommonAnnotationBeanPostProcessor, 只有将CommonAnnotationBeanPostProcessor添 加到容器,JSR150的相关注解才能发挥作用,通常如下添加相关配置即可:

既然不管是@Autowired还是@Resource都需要添加相应的BeanPostProcessor到容器,那么我们 就可以在基于XSD的配置文件中使用一个<Context: annotation-config>配置搞定以上所有的 BeanPostProcessor配置,如代码清单

<Context: annotation-config>不但帮我们把Autowired.AnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPost Processor和Required.AnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!

注意 Spring提供的@Autowired加上@Qualifier和JSR250提供的@Resource等注解属于两个 派系。如果要实现依赖注入的话,使用一个派别的注解就可以了。当然,既然<context:annotation-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形式)的配置文件中添加该项配置之后,class path-scanning功能立即开启:

现在<context:component-scan>将遍历扫描org.spring21路径下的所有类型定义,寻找标注了 相应注解的类,并添加到IoC容器。

提示 如果要扫描的类定义存在于不同的屈码包下面,也可以为base-package指定多个以逗号分隔的扫描路径。需要的话,不要犹豫!

<Context: component-scan>默认扫描的注解类型是@Component。不过,在@Component语义基 础上细化后的@Repository、@Service和@Controller也同样可以获得<Context:component-scan> 的青眯。@Compone址的语义更广、更宽泛,而@Repository、@Service和@Controller的语义则更具 体。所以,同样对于服务层的类定义来说,使用@Service标注它,要比使用@Component更为确切。 对于其他两种注解也是同样道理,我们暂且使用语义更广的@Component来标注FXNews相关类,以便 摆脱每次都要向IoC容器配置添加bean定义的苦恼。使用@Component标注后的FXNews相关类见代码清 单

<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>或者直接添加相关BeanPostProcessor的方式来满足@Autowired或者@Resource的需求,可以将<context: component-scan>的 annotation-config属性值从默认的true改为false。不过,我想没有太好的理由非要这么做吧?

<context: component-scan>的扫描行为可以进一步定制,默认情况下它只关心@Component、 @Repository、@Service和@Controller四位大员,但我们可以丰窜这一范围,或者对默认的扣描结 果进行过滤以排除某些类,<context: component-scan>的嵌套配置项可以帮我们达到这一目的。代 码清单演示了<context: component-scan>部分嵌套配置项的使用。

inclucte-filter和exclude-filter可以使用的type类型有annotation、assignable、regex和 aspectj四种。它们的更多信息可以参考最新的Spring 2.5参考文档。上例中,我们增加了@FXService 作为新的被扫描注解对象,并使用aspect]表达式排除某些扫描结果。

发布了524 篇原创文章 · 获赞 80 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/xushiyu1996818/article/details/103898614