Spring IoC容器之注解

Spring 2.5提供的基于注解的依赖注入功能延续了Spring框架内在IoC容器设计与实现上的一致性。 除了依赖关系的“表达”方式上的不同,底层的实现机制基本上保持一致。如果我们已经从Spring的 IoC容器的XML之旅中成功走过来,那么在体验基于注解的依赖注入的过程中,一定会发现许多似曾相识的身影

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

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

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

image.png

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

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


image.png

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

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


image.png

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

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

image.png

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

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

image.png

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

image.png

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

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

image.png

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

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

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


image.png

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

image.png

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

image.png

@Autowired之外的选择

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

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

image.png

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

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


image.png

如果想某个方法在对象实例化之后被调用,以做某些准备工作,或者想在对象销毁之前调用某个 方法清理某些资源,那么就可以像我们这样,使用@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的相关注解才能发挥作用,通常如下添加相关配置即可:

image.png

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

image.png

<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功能立即开启:

image.png

现在<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。

image.png

<context:component-scan>在扫描相关类定义并将它们添加到容器的时候,会使用一种默认的 命名规则,来生成那些添加到容器的bean定义的名称(beanName)。比如DowJonesNewsPersister通 过默认命名规则将获得dowJonesNewsPersister作为bean定义名称。如果想改变这一默认行为,就可 以像以上DowJonesNewsListener所对应的@Component那样,指定一个自定义的名称。

现在,除了<context:component-scan>是唯一需要添加到IoC容器的配置内容,所有的工作都 可以围绕着使用注解的Java源代码来完成了。如果现在加载配置文件,启动FXNewProvider来处理外 汇新闻的话,我们可以得到预期的运行效果,运行的代码如下所示:


image.png

你或许会觉得有些诧异,因为我们并没有使用<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提供 的最基本、也最为强大的表达方式了!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343