Spring的IoC容器之BeanFactory上

我们前面说过,Spring的IoC容器是一个IoC Service Provider,但是,这只是它被冠以IoC之名的部 分原因,我们不能忽略的是“容器”。Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本 的IoC支持,它作为轻量级容器还提供了IoC之外的支持。如在Spring的IoC容器之上,Spring还提供了 相应的AOP框架支持、企业级服务集成等服务。Spring的IoC容器和IoC Service Provider所提供的服务 之间存在一定的交集,二者的关系如图4-1所示。

image.png

Spring提供了两种容器类型:BeanFactory和ApplicationContext。

 BeanFactory。基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对 该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的 IoC容器选择。

 ApplicationContext。ApplicationContext在BeanFactory的基础上构建,是相对比较高 级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。ApplicationContext所管理 的对象,在该类型容器启动之后,默认全部初始化并绑定完成。所以,相对于BeanFactory来 说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容 器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中, ApplicationContext类型的容器是比较合适的选择。

image.png

ApplicationContext间接继承自BeanFactory,所以说它是构建于BeanFactory之上 的IoC容器。

BeanFactory,顾名思义,就是生产Bean的工厂。当然,严格来说,这个“生产过程”可能不像 说起来那么简单。既然Spring框架提倡使用POJO,那么把每个业务对象看作一个JavaBean对象,或许 更容易理解为什么Spring的IoC基本容器会起这么一个名字。作为Spring提供的基本的IoC容器, BeanFactory可以完成作为IoCServiceProvider的所有职责,包括业务对象的注册和对象间依赖关系的 绑定。

BeanFactory就像一个汽车生产厂。你从其他汽车零件厂商或者自己的零件生产部门取得汽车零 件送入这个汽车生产厂,最后,只需要从生产线的终点取得成品汽车就可以了。相似地,将应用所需 的所有业务对象交给BeanFactory之后,剩下要做的,就是直接从BeanFactory取得最终组装完成并 且可用的对象。至于这个最终业务对象如何组装,你不需要关心,BeanFactory会帮你搞定。

所以,对于客户端来说,与BeanFactory打交道其实很简单。最基本地,BeanFactory肯定会公 开一个取得组装完成的对象的方法接口,就像代码清单4-1中真正的BeanFactory的定义所展示的那样。

image.png

上面代码中的方法基本上都是查询相关的方法,例如,取得某个对象的方法(getBean)、查询 某个对象是否存在于容器中的方法(containsBean),或者取得某个bean的状态或者类型的方法等。 因为通常情况下,对于独立的应用程序,只有主入口类才会跟容器的API直接耦合

拥有BeanFactory之后的生活

确切地说,拥有BeanFactory之后的生活没有太大的变化。当然,我的意思是看起来没有太大的 变化。到底引入BeanFactory后的生活是什么样子,让我们一起来体验一下吧!

依然“拉拉扯扯的事情”。对于应用程序的开发来说,不管是否引入BeanFactory之类的轻量级 容器,应用的设计和开发流程实际上没有太大改变。换句话说,针对系统和业务逻辑,该如何设计和 实现当前系统不受是否引入轻量级容器的影响。对于我们的FX新闻系统,我们还是会针对系统需求, 分别设计相应的接口和实现类。前后唯一的不同,就是对象之间依赖关系的解决方式改变了。这就是 所谓的“拉拉扯扯的事情”。之前我们的系统业务对象需要自己去“拉”(Pull)所依赖的业务对象, 有了BeanFactory之类的IoC容器之后,需要依赖什么让BeanFactory为我们推过来(Push)就行了。 所以,简单点儿说,拥有BeanFactory之后,要使用IoC模式进行系统业务对象的开发。(实际上, 即使不使用BeanFactory之类的轻量级容器支持开发,开发中也应该尽量使用IoC模式。)

既然使用IoC模式开发的业务对象现在不用自己操心如何解 决相互之间的依赖关系,那么肯定得找人来做这个工作。毕竟,工作最终是要有人来做的,大家都不动 手,那工作就不能进行了。当BeanFactory说这些事情让它来做的时候,可能没有告诉你它会怎么来做 这个事情。不过没关系,稍后我会详细告诉你它是如何做的。通常情况下,它会通过常用的XML文件来 注册并管理各个业务对象之间的依赖关系,就像代码清单4-3所演示的那样。

image.png

在BeanFactory出现之前,我们通常会直接在应用程序的入口类的main方法中, 自己实例化相应的对象并调用之,如以下代码所示:

FXNewsProvider newsProvider = new FXNewsProvider(); newsProvider.getAndPersistNews();

不过,现在既然有了BeanFactory,我们通常只需将“生产线图纸”交给BeanFactory,让 BeanFactory为我们生产一个FXNewsProvider,如以下代码所示


image.png

这就是拥有BeanFactory后的生活。当然,这只是使用BeanFactory后开发流程的一个概览而已, 具体细节请容我慢慢道来。

BeanFactory的对象注册与依赖绑定方式

BeanFactory作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的 依赖绑定关系,同样需要某种途径来记录和管理这些信息。前面在介绍IoC Service Provider时,我 们提到通常会有三种方式来管理这些信息。而BeanFactory几乎支持所有这些方式,很令人兴奋,不 是吗?

4.2.1 直接编码方式

其实,把编码方式单独提出来称作一种方式并不十分恰当。因为不管什么方式,最终都需要编码 才能“落实”所有信息并付诸使用。不过,通过这些代码,起码可以让我们更加清楚BeanFactory在 底层是如何运作的。

下面来看一下我们的FX新闻系统相关类是如何注册并绑定的(见代码清单4-4)。

image.png
image.png

BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,Default- ListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBean- Factory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才 是在BeanFactory的实现中担当Bean注册管理的角色。基本上,BeanFactory接口只定义如何访问容 器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。 BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现 5 类会实现这个接口来管理Bean的注册。它们之间的关系如图4-3所示。


image.png

打个比方说,BeanDefinitionRegistry就像图书馆的书架,所有的书是放在书架上的。虽然你 还书或者借书都是跟图书馆(也就是BeanFactory,或许BookFactory可能更好些)打交道,但书架才 是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”

每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该 BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象 12 类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会 通过这些信息为客户端返回一个完备可用的对象实例。RootBeanDefinition和ChildBean- Definition是BeanDefinition的两个主要实现类。

现在,我们再来看这段绑定代码,应该就有“柳暗花明”的感觉了。

  • 在 main 方 法 中 , 首 先 构 造 一 个 DefaultListableBeanFactory 作 为 BeanDefinition-
    Registry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过 bindViaCode返回的BeanFactory取得需要的对象,最后执行相应逻辑。在我们的实例里,当 然就是取得FXNewsProvider进行新闻的处理。

  • 在bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用 了RootBeanDefinition作为BeanDefinition的实现类。构造完成后,将这些 BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。之后,因为我 们的FXNewsProvider是采用的构造方法注入,所以,需要通过ConstructorArgument-Values为其注入相关依赖。在这里为了同时说明setter方法注入,也同时展示了在Spring中如 何使用代码实现setter方法注入。如果要运行这段代码,需要把setter方法注入部分的4行代码注 释掉。最后,以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefini- tionRegistry实例。

4.2.2 外部配置文件方式

Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。当然,如果你愿 意也可以引入自己的文件格式,前提是真的需要。

采用外部配置文件时,Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外 部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实 现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注 册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。 当然,大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinition- Reader的相应实现类来做的,BeanDefinitionRegistry只不过负责保管而已。整个过程类似于如下 代码:

image.png

1. Properties配置格式的加载
Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinition- Reader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader, 只要根据该类的读取规则,提供相应的配置文件即可。
对于FXNews系统的业务对象,我们采用如下文件内容(见代码清单4-5)进行配置加载。

image.png

这些内容是特定于Spring的PropertiesBeanDefinitionReader的,要了解更多内容,请参照Spring的API参考文档。我们可以很容易地看明白代码清单4-5中的配置内容所要表达的意思。

  • djNewsProvider作为beanName,后面通过.(class)表明对应的实现类是什么,实际上使用 djNewsProvider.class=...的形式也是可以的,但Spring 1.2.6之后不再提倡使用,而提倡使
    用.(class)的形式。其他两个类的注册,djListener和djPersister,也是相同的道理。

  • 通过在表示beanName的名称后添加.[number]后缀的形式,来表示当前beanName对应的对 象需要通过构造方法注入的方式注入相应依赖对象。在这里,我们分别将构造方法的第一个 参数和第二个参数对应到djListener和djPersister。需要注意的一点,就是0和$1后面的 (ref),(ref)用来表示所依赖的是引用对象,而不是普通的类型。如果不加(ref), PropertiesBeanDefinitionReader会将djListener和djPersister作为简单的String类型 进行注入,异常自然不可避免啦。

  • FXNewsProvider采用的是构造方法注入,而为了演示setter方法注入在Properties配置文件中又 是一个什么样子,以便于你更全面地了解基于Properties文件的配置方式,我们在下面增加了 setter方法注入的例子,不过进行了注释。实际上,与构造方法注入最大的区别就是,它不使 用数字顺序来指定注入的位置,而使用相应的属性名称来指定注入。newsListener和 newPersistener恰好就是我们的FXNewsProvider类中所声明的属性名称。这印证了之前在 比较构造方法注入和setter方法注入方式不同时提到的差异,即构造方法注入无法通过参数名 称来标识注入的确切位置,而setter方法注入则可以通过属性名称来明确标识注入。与在 Properties中表达构造方法注入一样,同样需要注意,如果属性名称所依赖的是引用对象,那 么一定不要忘了(ref)。

当这些对象之间的注册和依赖注入信息都表达清楚之后,就可以将其加载到BeanFactory而付诸 使用了。而这个加载过程实际上也就像我们之前总体上所阐述的那样,代码清单4-6中的内容再次演示 了类似的加载过程。

image.png

基于Properties的加载方式就是这么简单,所有的信息配置到Properties文件即可,不用再通过冗长 的代码来完成对象的注册和依赖绑定。这些工作就交给相应的BeanDefinitionReader来做吧!哦, 我的意思是,让给PropertiesBeanDefinitionReader来做。

注意 Spring提供的PropertiesBeanDefinitionReader是按照Spring自己的文件配置规则进 行加载的,而同样的道理,你也可以按照自己的规则1来提供相应的Properties配置文件。只不 过,现在需要实现你自的“PropertiesBeanDefinitionReader”来读取并解析。这当然有“重新 发明轮子”之嫌,不过,如果你只是想试验一下,也可以尝试哦。无非就是按照自己的规则把 各个业务对象信息读取后,将编码方式的代码改造一下放到你自己的“PropertiesBean- DefinitionReader”而已。

2. XML配置格式的加载

XML配置格式是Spring支持最完整,功能最强大的表达方式。当然,一方面这得益于XML良好的 语意表达能力;另一方面,就是Spring框架从开始就自始至终保持XML配置加载的统一性。同Properties 配置加载类似,现在只不过是转而使用XML而已。Spring 2.x之前,XML配置文件采用DTD(Document Type Definition)实现文档的格式约束。2.x之后,引入了基于XSD(XML Schema Definition)的约束 方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变,所以, 后面的大部分讲解还会沿用DTD的方式,只有必要时才会给出特殊说明。

如果FX新闻系统对象按照XML配置方式进行加载的话,配置文件内容如代码清单4-7所示。

image.png

我想这段内容不需要特殊说明吧,应该比Properties文件的内容要更容易理解

有了XML配置文件,我们需要将其内容加载到相应的BeanFactory实现中,以供使用,如代码清 单4-8所示。

image.png

与为Properties配置文件格式提供PropertiesBeanDefinitionReader相对应,Spring同样为XML 格 式 的 配 置 文 件 提 供 了 现 成 的 BeanDefinitionReader 实 现 , 即 XmlBeanDefinitionReader 。 XmlBeanDefinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内 容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中(在这里是Default- ListableBeanFactory)。这时,整个BeanFactory就可以放给客户端使用了。

除了提供XmlBeanDefinitionReader用于XML格式配置文件的加载,Spring还在Default- ListableBeanFactory的基础上构建了简化XML格式配置加载的XmlBeanFactory实现。从以上代码 最后注释掉的一行,你可以看到使用了XmlBeanFactory之后,完成XML的加载和BeanFactory的初 始化是多么简单。

注意 当然,如果你愿意,就像Properties方式可以扩展一样,XML方式的加载同样可以扩展。 虽然XmlBeanFactory基本上已经十分完备了,但如果出于某种目的,XmlBeanFactory或者默 认的XmlBeanDefinitionReader所使用的XML格式无法满足需要的话,你同样可以通过扩展 10 XmlBeanDefinitionReader或者直接实现自己的BeanDefinitionReader来达到自定义XML 配置文件加载的目的。Spring的可扩展性为你服务

注解方式

可能你没有注意到,我在提到BeanFactory所支持的对象注册与依赖绑定方式的时候,说的是 BeanFactory“几乎”支持IoC Service Provider可能使用的所有方式。之所以这么说,有两个原因。

  • 在Spring 2.5发布之前,Spring框架并没有正式支持基于注解方式的依赖注入;
  • Spring 2.5发布的基于注解的依赖注入方式,如果不使用classpath-scanning功能的话,仍然部分依赖于“基于XML配置文件”的依赖注入方式。

另外,注解是Java 5之后才引入的,所以,以下内容只适用于应用程序使用了Spring 2.5以及Java 5或者更高版本的情况之下。

如果要通过注解标注的方式为FXNewsProvider注入所需要的依赖,现在可以使用@Autowired以及@Component对相关类进行标记。代码清单4-9演示了FXNews相关类使用指定注解标注后的情况。

image.png

@Autowired是这里的主角,它的存在将告知Spring容器需要为当前对象注入哪些依赖对象。而 @Component则是配合Spring 2.5中新的classpath-scanning功能使用的。现在我们只要再向Spring的配置 文件中增加一个“触发器”,使用@Autowired和@Component标注的类就能获得依赖对象的注入了。 代码清单4-10给出的正是针对这部分功能的配置内容。

image.png

<context:component-scan/>会到指定的包(package)下面扫描标注有@Component的类,如果 找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依 赖对象。

在以上所有这些工作都完成之后,我们就可以像通常那样加载配置并执行当前应用程序了,如以
下代码所示:

image.png

最后将详细讲解Spring 2.5新引入的“基于注解的依赖注入”。当前的内容只是让我们先从总 体上有一个大概的印象,所以,不必强求自己现在就完全理解它们。

BeanFactory的XML之旅

XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。从Spring的参考 文档到各Spring相关书籍,都是按照XML的配置进行说明的,这部分内容可以让你充分领略到Spring 的IoC容器的魅力,以致于我们也不得不带你初次或者再次踏上Spring的XML之旅。

4.3.1 <beans>和<bean>

所有使用 XML 文件进行配置信息加载的 Spring IoC 容器,包括BeanFactory和 ApplicationContext的所有XML相应实现,都使用统一的XML格式。在Spring 2.0版本之前,这种格 式由Spring提供的DTD规定,也就是说,所有的Spring容器加载的XML配置文件的头部,都需要以下 形式的DOCTYPE声明:


image.png

从Spring 2.0版本之后,Spring在继续保持向前兼容的前提下,既可以继续使用DTD方式的DOCTYPE 进行配置文件格式的限定,又引入了基于XML Schema的文档声明。所以,Spring 2.0之后,同样可以 使用代码清单4-11所展示的基于XSD的文档声明。

image.png

不过,不管使用哪一种形式的文档声明,实际上限定的元素基本上是相同的。让我们从最顶层的
元素开始,看一下这两种文档声明都限定了哪些元素吧!

所有注册到容器的业务对象,在Spring中称之为Bean。所以,每一个对象在XML中的映射也自然 而然地对应一个叫做<bean>的元素。既然容器最终可以管理所有的业务对象,那么在XML中把这些 叫做<bean>的元素组织起来的,就叫做<beans>。多个<bean>组成一个<beans>很容易理解,不是吗?

  1. <beans>之唯我独尊

<beans>是XML配置文件中最顶层的元素,它下面可以包含0或者1个<description>和多个<bean>以及<import>或者<alias>,如图4-4所示。

image.png

<beans>作为所有<bean>的“统帅”,它拥有相应的属性(attribute)对所辖的<bean>进行统一 的默认行为设置,包括如下几个。

  • default-lazy-init。其值可以指定为true或者false,默认值为false。用来标志是否对所 有的<bean>进行延迟初始化。

  • default-autowire。可以取值为no、byName、byType、constructor以及autodetect。默 认值为no,如果使用自动绑定的话,用来标志全体bean使用哪一种默认绑定方式。

  • default-dependency-check。可以取值none、objects、simple以及all,默认值为none, 即不做依赖检查。

  • default-init-method。如果所管辖的<bean>按照某种规则,都有同样名称的初始化方法的 话,可以在这里统一指定这个初始化方法名,而不用在每一个<bean>上都重复单独指定。

  • default-destroy-method。与default-init-method相对应,如果所管辖的bean有按照某种规则使用了相同名称的对象销毁方法,可以通过这个属性统一指定。

  1. <description>、<import>和<alias>
    之所以把这几个元素放到一起讲解,是因为通常情况下它们不是必需的。不过,了解一下也没什
    么不好,不是吗?
     <description> 可以通过<description>在配置的文件中指定一些描述性的信息。通常情况下,该元素是省略的

 <import>
通常情况下,可以根据模块功能或者层次关系,将配置信息分门别类地放到多个配置文件中。在 想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在这个主要的配置文件 中通过<import>元素对其所依赖的配置文件进行引用。比如,如果A.xml中的<bean>定义可能依赖 B.xml中的某些<bean>定义,那么就可以在A.xml中使用<import>将B.xml引入到A.xml,以类似于 <import resource="B.xml"/>的形式。

但是,这个功能在我看来价值不大,因为容器实际上可以同时加载多个配置,没有必要非通过一个配置文件来加载所有配置。不过,或许在有些场景中使用这种方式比较方便也说不定。

 <alias>
可以通过<alias>为某些<bean>起一些“外号”(别名),通常情况下是为了减少输入。比如,假设有个<bean>,它的名称为dataSourceForMasterDatabase,你可以为其添加一个<alias>,像 这样<alias name="dataSourceForMasterDatabase" alias="masterDataSource"/>。以后通过 dataSourceForMasterDatabase或者masterDataSource来引用这个<bean>都可以,只要你觉得方便 就行。

孤孤单单一个人

哦,错了,是孤孤单单一个Bean。每个业务对象作为个体,在Spring的XML配置文件中是与<bean> 11 元素一一对应的。窥一斑而知全豹,只要我们了解单个的业务对象是如何配置的,剩下的就可以“依葫芦画瓢”了。所以,让我们先从最简单的单一对象配置开始吧!如下代码演示了最基础的对象配置 形式:

image.png
  • id属性
    通常,每个注册到容器的对象都需要一个唯一标志来将其与“同处一室”的“兄弟们”区分开来, 14
    就好像我们每一个人都有一个身份证号一样(重号的话就比较麻烦)。通过id属性来指定当前注册对 象的beanName是什么。这里,通过id指定beanName为djNewsListener。实际上,并非任何情况下都 需要指定每个<bean>的id,有些情况下,id可以省略,比如后面会提到的内部<bean>以及不需要根据 beanName明确依赖关系的场合等。

除了可以使用id来指定<bean>在容器中的标志,还可以使用name属性来指定<bean>的别名 (alias)。比如,以上定义,我们还可以像如下代码这样,为其添加别名:

image.png

与id属性相比,name属性的灵活之处在于,name可以使用id不能使用的一些字符,比如/。而且 还可以通过逗号、空格或者冒号分割指定多个name。name的作用跟使用<alias>为id指定多个别名基 本相同:


image.png
  • class属性
    每个注册到容器的对象都需要通过<bean>元素的class属性指定其类型,否则,容器可不知道这个对象到底是何方神圣。在大部分情况下,该属性是必须的。仅在少数情况下不需要指定,如后面将提到的在使用抽象配置模板的情况下。

Help Me, Help You1

在大部分情况下,你不太可能选择单独“作战”,业务对象也是;各个业务对象之间会相互协作 来更好地完成同一使命。这时,各个业务对象之间的相互依赖就是无法避免的。对象之间需要相互协 作,在横向上它们存在一定的依赖性。而现在我们就是要看一下,在Spring的IoC容器的XML配置中, 应该如何表达这种依赖性。

既然业务对象现在都符合IoC的规则,那么要了解的表达方式其实也很简单,无非就是看一下构 造方法注入和setter方法注入通过XML是如何表达的而已。那么,让我们开始吧!

  1. 构造方法注入的XML之道

按照Spring的IoC容器配置格式,要通过构造方法注入方式,为当前业务对象注入其所依赖的对象, 需要使用<constructor-arg>。正常情况下,如以下代码所示:


image.png

对于<ref>元素,稍后会进行详细说明。这里你只需要知道,通过这个元素来指明容器将为
djNewsProvider这个<bean>注入通过<ref>所引用的Bean实例。这种方式可能看起来或者编写起来 不是很简洁,最新版本的Spring也支持配置简写形式,如以下代码所示:

image.png

简洁多了不是嘛?其实,无非就是表达方式不同而已,实际达到的效果是一样的。有些时候,容器在加载XML配置的时候,因为某些原因,无法明确配置项与对象的构造方法参数 列表的一一对应关系,就需要请<constructor-arg>的type或者index属性出马。比如,对象存在多个构造方法,当参数列表数目相同而类型不同的时候,容器无法区分应该使用哪个构造方法来实例化对象,或者构造方法可能同时传入最少两个类型相同的对象。

  • type属性
    假设有一个对象定义如代码清单4-12所示。


    image.png

该类声明了两个构造方法,分别都只是传入一个参数,且参数类型不同。这时,我们可以进行配置,如以下代码所示:


image.png

如果从BeanFactory取得该对象并调用toString()查看的话,我们会发现Spring调用的是第一个 构造方法,因为输出是如下内容:

..MockBusinessObject@f73c1[dependency1=111111,dependency2=0]

但是,如果我们想调用的却是第二个传入int类型参数的构造方法,又该如何呢?可以使用type属 性,通过指定构造方法的参数类型来解决这一问题,配置内容如下代码所示:

image.png

现在,我们得到了自己想要的对象实例,如下的控制台输出信息印证了这一点:
..MockBusinessObject@f73c1[dependency1=<null>,dependency2=111111]

  • index属性
    当某个业务对象的构造方法同时传入了多个类型相同的参数时,Spring又该如何将这些配置中的 信息与实际对象的参数一一对应呢?好在,如果配置项信息和对象参数可以按照顺序初步对应的话,Spring还是可以正常工作的,如代码清单4-13所示。
image.png

那么,我们可以得到如下对象:
dependency2)

..MockBusinessObject@1ef8cf3[dependency1=11111,dependency2=22222]
但是,如果要让“11111”作为对象的第二个参数,而将“22222”作为第一个参数来构造对象, 又该如何呢?好!可以颠倒配置项,如以下代码所示:
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg value="22222"/> <constructor-arg value="11111"/>
</bean>
不过,还有一种方式,那就是像如下代码所示的那样,使用index属性:
<bean id="mockBO" class="..MockBusinessObject"> <constructor-arg index="1" value="11111"/> <constructor-arg index="0" value="22222"/>
</bean>

这时,同样可以得到想要的对象实例,以下控制台输出表明了这一点:
..MockBusinessObject@ecd7e[dependency1=22222,dependency2=11111]

5. autowire

除了可以通过配置明确指定bean之间的依赖关系,Spirng还提供了根据bean定义的某些特点将相 互依赖的某些bean直接自动绑定的功能。通过<bean>的autowire属性,可以指定当前bean定义采用某 种类型的自动绑定模式。这样,你就无需手工明确指定该bean定义相关的依赖关系,从而也可以免去 一些手工输入的工作量。

Spring提供了5种自动绑定模式,即no、byName、byType、constructor和autodetect,下面是 它们的具体介绍。

no
容器默认的自动绑定模式,也就是不采用任何形式的自动绑定,完全依赖手工明确配置各个bean
之间的依赖关系,以下代码演示的两种配置是等效的:

<bean id="beanName" class="..."/>
或者
<bean id="beanName" class="..." autowire="no"/>

 byName
按照类中声明的实例变量的名称,与XML配置文件中声明的bean定义的beanName的值进行匹配,相匹配的bean定义将被自动绑定到当前实例变量上。这种方式对类定义和配置的bean定义有一定的限 制。假设我们有如下所示的类定义:

image.png
  • byType
    如果指定当前bean定义的autowire模式为byType,那么,容器会根据当前bean定义类型,分析其相应的依赖对象类型,然后到容器所管理的所有bean定义中寻找与依赖对象类型相同的bean定义,然后将找到的符合条件的bean自动绑定到当前bean定义

对于byName模式中的实例类Foo来说,容器会在其所管理的所有bean定义中寻找类型为Bar的bean定义。如果找到,则将找到的bean绑定到Foo的bean定义;如果没有找到,则不做设置。但如果找到 多个,容器会告诉你它解决不了“该选用哪一个”的问题,你只好自己查找原因,并自己修正该问题。 所以,byType只能保证,在容器中只存在一个符合条件的依赖对象的时候才会发挥最大的作用,如果 容器中存在多个相同类型的bean定义,那么,不好意思,采用手动明确配置吧!

image.png
  • constructor
    byName和byType类型的自动绑定模式是针对property的自动绑定,而constructor类型则是针对构造方法参数的类型而进行的自动绑定,它同样是byType类型的绑定模式。不过,constructor是匹 配构造方法的参数类型,而不是实例属性的类型。与byType模式类似,如果找到不止一个符合条件的 bean定义,那么,容器会返回错误。使用上也与byType没有太大差别,只不过是应用到需要使用构造 方法注入的bean定义之上,代码清单4-23给出了一个使用construtor模式进行自动绑定的简单场景演 示。
image.png

 autodetect
这种模式是byType和constructor模式的结合体,如果对象拥有默认无参数的构造方法,容器会优先考虑byType的自动绑定模式。否则,会使用constructor模式。当然,如果通过构造方法注入绑 定后还有其他属性没有绑定,容器也会使用byType对剩余的对象属性进行自动绑定。

  1. 手工明确指定的绑定关系总会覆盖自动绑定模式的行为。
  2. 自动绑定只应用于“原生类型、String类型以及Classes类型以外”的对象类型,对“原生类型、String类型和Classes类型”以及“这些类型的数组”应用自动绑定是无效的。

自动绑定和手动明确绑定各有利弊。自动绑定的优点有如下两点。
(1) 某种程度上可以有效减少手动敲入配置信息的工作量。
(2) 某些情况下,即使为当前对象增加了新的依赖关系,但只要容器中存在相应的依赖对
象,就不需要更改任何配置信息。
自动绑定的缺点有如下几点。
(1) 自动绑定不如明确依赖关系一目了然。我们可以根据明确的依赖关系对整个系统有一
个明确的认识,但使用自动绑定的话,就可能需要在类定义以及配置文件之间,甚至各个配置 文件之间来回转换以取得相应的信息。
(2) 某些情况下,自动绑定无法满足系统需要,甚至导致系统行为异常或者不可预知。根 据类型(byType)匹配进行的自动绑定,如果系统中增加了另一个相同类型的bean定义,那么 整个系统就会崩溃;根据名字(byName)匹配进行的自动绑定,如果把原来系统中相同名称的 bean定义类型给换掉,就会造成问题,而这些可能都是在不经意间发生的。
(3) 使用自动绑定,我们可能无法获得某些工具的良好支持,比如Spring IDE。

通常情况下,只要有良好的XML编辑器支持,我不会介意多敲那几个字符。起码自己可以对整个 系统的行为有完全的把握。当然,任何事物都不绝对,只要根据相应场景找到合适的就可以。噢,对了,差点儿忘了!作为所有<bean>的统帅,<beans>有一个default-autowire属性,它 可以帮我们省去为多个<bean>单独设置autowire属性的麻烦,default-autowire的默认值为no,即 不进行自动绑定。如果想让系统中所有的<bean>定义都使用byType模式的自动绑定,我们可以使用 如下配置内容:


image.png
7. lazy-init

延迟初始化(lazy-init)这个特性的作用,主要是可以针对ApplicationContext容器的bean 初始化行为施以更多控制。与BeanFactory不同,ApplicationContext在容器启动的时候,就会马 上对所有的“singleton的bean定义”1 进行实例化操作。通常这种默认行为是好的,因为如果系统有问 题的话,可以在第一时间发现这些问题,但有时,我们不想某些bean定义在容器启动后就直接实例化, 可能出于容器启动时间的考虑,也可能出于其他原因的考虑。总之,我们想改变某个或者某些bean定 义在ApplicationContext容器中的默认实例化时机。这时,就可以通过<bean>的lazy-init属性来 控制这种初始化行为,如下代码所示:

<bean id="lazy-init-bean" class="..." lazy-init="true"/> <bean id="not-lazy-init-bean" class="..."/>

这样,ApplicationContext容器在启动的时候,只会默认实例化not-lazy-init-bean而不会实 例化lazy-init-bean。

当然,仅指定lazy-init-bean的lazy-init为true,并不意味着容器就一定会延迟初始化该bean 7 的实例。如果某个非延迟初始化的bean定义依赖于lazy-init-bean,那么毫无疑问,按照依赖决计 的顺序,容器还是会首先实例化lazy-init-bean,然后再实例化后者,如下代码演示了这种相互牵 连导致延迟初始化失败的情况:


image.png

虽然lazy-init-bean是延迟初始化的,但因为依赖它的not-lazy-init-bean并不是延迟初始 化,所以lazy-init-bean还是会被提前初始化,延迟初始化的良好打算“泡汤”。如果我们真想保 证lazy-init-bean一定会被延迟初始化的话,就需要保证依赖于该bean定义的其他bean定义也同样设 置为延迟初始化。在bean定义很多时,好像工作量也不小哦。不过不要忘了,<beans>可是所有<bean> 的统领啊,让它一声令下吧!如代码清单4-24所演示的,在顶层由<beans>统一控制延迟初始化行为 即可。

image.png

bean 的 scope

BeanFactory除了拥有作为IoC Service Provider的职责,作为一个轻量级容器,它还有着其他一些 职责,其中就包括对象的生命周期管理。

本节主要讲述容器中管理的对象的scope这个概念。多数中文资料在讲解bean的scope时喜欢用“作 用域”这个名词,应该还算贴切吧。不过,我更希望告诉你scope这个词到底代表什么意思,至于你怎 么称呼它反而不重要。

scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。打个比方吧!我们都是处于社会(容器)中,如果把中学教师作为一个类定义,那么当容 器初始化这些类之后,中学教师只能局限在中学这样的场景中;中学,就可以看作中学教师的scope。

Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另 外三种scope类型,即request、session和global session类型。不过这三种类型有所限制,只能在Web应 用中使用。也就是说,只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。

我们可以通过使用<bean>的singleton或者scope属性来指定相应对象的scope,其中,scope属性只 能在XSD格式的文档声明中使用,类似于如下代码所演示的形式:

image.png

让我们来看一下容器提供的这几个scope是如何限定相应对象的吧!

  1. singleton

配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象。但是要根据这个模板
构造多少对象实例,又该让这些构造完的对象实例存活多久,则由容器根据bean定义的scope语意来决 定。标记为拥有singleton scope的对象定义,在Spring的IoC容器中只存在一个实例,所有对该对象的引 用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出, 也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

图4-5是Spring参考文档中所给出的singleton的bean的实例化和注入语意演示图例,或许可以更形 象地说明问题。


image.png

需要注意的一点是,不要因为名字的原因而与GoF 1 所提出的Singleton模式相混淆,二者的语意是 不同的:标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例; 而Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例。

可以从两个方面来看待singleton的bean所具有的特性。

  • 对象实例数量。singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。这就好像每个幼儿园都会有一个滑梯一样,这个幼儿园的小朋友共同使用这一个滑梯。而对于该幼儿园容器来说,滑梯实际上就是一个singleton的bean。

  • 对象存活时间。singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。

通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope,所以,下面三种配置形式实际上达成的是同样的效果:


image.png
  1. prototype
    针对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新 生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责 的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请 求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每 次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。

让我们继续幼儿园的比喻,看看prototype在这里应该映射到哪些事物。儿歌里好像有句“排排坐, 分果果”,我们今天要分苹果咯!将苹果的bean定义的scope声明为prototype,在每个小朋友领取苹果 的时候,我们都是分发一个新的苹果给他。发完之后,小朋友爱怎么吃怎么吃,爱什么时候吃什么时 候吃。但是,吃完后要记得把果核扔到果皮箱哦! 而如果你把苹果的bean定义的scope声明为singleton 9 会是什么情况呢?如果第一个小朋友比较谦让,那么他可能对这个苹果只咬一口,但是下一个小朋友 吃多少就不知道了。当吃得只剩一个果核的时候,下一个来吃苹果的小朋友肯定要哭鼻子的。

所以,对于那些请求方不能共享使用的对象类型,应该将其bean定义的scope设置为prototype。这 样,每个请求方可以得到自己对应的一个对象实例,而不会出现上面“哭鼻子”的现象。通常,声明 为prototype的scope的bean定义类型,都是一些有状态的,比如保存每个顾客信息的对象。

从Spring 参考文档上的这幅图片(见图4-6),你可以再次了解一下拥有prototype scope的bean定义,在实例化对象并注入依赖的时候,它的具体语意是个什么样子。

image.png

你用以下形式来指定某个bean定义的scope为prototype类型,效果是一样的:


image.png
  1. request、session和global session
    这三个scope类型是Spirng 2.0之后新增加的,它们不像之前的singleton和prototype那么“通用”, 因为它们只适用于Web应用程序,通常是与XmlWebApplicationContext共同使用.

  2. 自定义scope类型

在Spring 2.0之后的版本中,容器提供了对scope的扩展点,这样,你可以根据自己的需要或者应 用的场景,来添加自定义的scope类型。需要说明的是,默认的singleton和prototype是硬编码到代码中 的,而request、session和global session,包括自定义scope类型,则属于可扩展的scope行列,它们都实 现了org.springframework.beans.factory.config.Scope接口,该接口定义如下:

image.png

要实现自己的scope类型,首先需要给出一个Scope接口的实现类,接口定义中的4个方法并非都 是必须的,但get和remove方法必须实现。我们可以看一下http://www.jroller.com/eu/entry/implementing _efficinet_id_generator中提到的一个ThreadScope的实现

image.png
image.png

有了Scope的实现类之后,我们需要把这个Scope注册到容器中,才能供相应的bean定义使用。通 常情况下,我们可以使用ConfigurableBeanFactory的以下方法注册自定义scope:
void registerScope(String scopeName, Scope scope);

其中,参数scopeName就是使用的bean定义可以指定的名称,比如Spring框架默认提供的自定义scope 类型request或者session。参数scope即我们提供的Scope实现类实例。

工厂方法与 FactoryBean

在强调“面向接口编程”的同时,有一点需要注意:虽然对象可以通过声明接口来避免对特定接 口实现类的过度耦合,但总归需要一种方式将声明依赖接口的对象与接口实现类关联起来。否则,只 依赖一个不做任何事情的接口是没有任何用处的。假设我们有一个像代码清单4-30所声明的Foo类, 它声明了一个BarInterface依赖。


image.png

如果该类是由我们设计并开发的,那么还好说,我们可以通过依赖注入,让容器帮助我们解除接 口与实现类之间的耦合性。但是,有时,我们需要依赖第三方库,需要实例化并使用第三方库中的相 关类,这时,接口与实现类的耦合性需要其他方式来避免。

通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口 实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主 体对象不需要做任何变动。代码清单4-31演示了这种做法。

image.png

针对使用工厂方法模式实例化对象的方式,Spring的IoC容器同样提供了对应的集成支持。我们所 要做的,只是将工厂类所返回的具体的接口实现类注入给主体对象(这里是Foo)。

  1. 静态工厂方法(Static Factory Method)
    假设某个第三方库发布了BarInterface,为了向使用该接口的客户端对象屏蔽以后可能对 BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类StaticBarInterface- Factory,代码如下:
image.png

为了将该静态工厂方法类返回的实现注入Foo,我们使用以下方式进行配置(通过setter方法注入 方式为Foo注入BarInterface的实例):


image.png

class指定静态方法工厂类,factory-method指定工厂方法名称,然后,容器调用该静态方法工 厂类的指定工厂方法(getInstance),并返回方法调用后的结果,即BarInterfaceImpl的实例。 也就是说,为foo注入的bar实际上是BarInterfaceImpl的实例,即方法调用后的结果,而不是静态 工厂方法类(StaticBarInterfaceFactory)。我们可以实现自己的静态工厂方法类返回任意类型 的对象实例,但工厂方法类的类型与工厂方法返回的类型没有必然的相同关系。

某些时候,有的工厂类的工厂方法可能需要参数来返回相应实例,而不一定非要像我们的 getInstance()这样没有任何参数。对于这种情况,可以通过<constructor-arg>来指定工厂方法需 要的参数,比如现在StaticBarInterfaceFactory需要其他依赖来返回某个BarInterface的实现, 其定义可能如下:

image.png

唯一需要注意的就是,针对静态工厂方法实现类的bean定义,使用<constructor-arg>传入的是 工厂方法的参数,而不是静态工厂方法实现类的构造方法的参数。(况且,静态工厂方法实现类也没 有提供显式的构造方法。

  1. 非静态工厂方法(Instance Factory Method)

既然可以将静态工厂方法实现类的工厂方法调用结果作为bean注册到容器中,我们同样可以针对 基于工厂类实例的工厂方法调用结果应用相同的功能,只不过,表达方式可能需要稍微变一下。
现在为BarInterface提供非静态的工厂方法实现类,该类定义如下代码所示:


image.png

NonStaticBarInterfaceFactory是作为正常的bean注册到容器的,而bar的定义则与静态工厂方 法的定义有些不同。现在使用factory-bean属性来指定工厂方法所在的工厂类实例,而不是通过 class属性来指定工厂方法所在类的类型。指定工厂方法名则相同,都是通过factory-method属性进 行的。

如果非静态工厂方法调用时也需要提供参数的话,处理方式是与静态的工厂方法相似的,都可以 通过<constructor-arg>来指定方法调用参数。

3. FactoryBean

FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注 12 册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。

FactoryBean

FactoryBean是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口,请不要将其与容器名称BeanFactory相混淆。FactoryBean,其主语是Bean,定语为Factory,也就是说,它本身与其他注册到容器的对象一样,只是一个Bean而已,只不过,这种类型的Bean本身就是生产对象的工厂(Factory)。

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.spring- framework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码。当然,不使用Fac-toryBean,而像通常那样实现自定义的工厂方法类也是可以的。不过,FactoryBean可是Spring提供 的对付这种情况的“制式装备”哦!

要 实 现 并 使 用 自 己 的 FactoryBean 其 实 很 简 单 , org.springframework.beans.factory. FactoryBean只定义了三个方法,如以下代码所示:


image.png

getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现该方法以给出自己 的对象实例化逻辑;getObjectType()方法仅返回getObject()方法所返回的对象的类型,如果预先 无法确定,则返回null;isSingleton()方法返回结果用于表明,工厂方法(getObject())所“生 产”的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回true,否则返回false;

如果我们想每次得到的日期都是第二天,可以实现一个如代码清单4-33所示的FactoryBean。

image.png

配置上看不出与平常的bean定义有何不同,不过,只有当我们看到NextDayDateDisplayer的定 义的时候,才会知道FactoryBean的魔力到底在哪。NextDayDateDisplayer的定义如下:


image.png

看到了嘛?NextDayDateDisplayer所声明的依赖dateOfNextDay的类型为DateTime,而不是 NextDayDateFactoryBean。也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。

如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加前缀&来达到目的。代码清单4-34展示了获取FactoryBean本身与获取FactoryBean“生产”的对象之间的差别。

image.png

Spring容器内部许多地方了使用FactoryBean。下面是一些比较常见的FactoryBean实现,你可 以参照FactoryBean的Javadoc以了解更多内容。

image.png

偷梁换柱之术

在学习以下内容之前,先提一下有关bean的scope的使用“陷阱”,特别是prototype在容器中的使 用,以此引出本节将要介绍的Spring容器较为独特的功能特性:方法注入(Method Injection)以及方 法替换(Method Replacement)。

我们知道,拥有prototype类型scope的bean,在请求方每次向容器请求该类型对象的时候,容器都 会返回一个全新的该对象实例。为了简化问题的叙述,我们直接将FX News系统中的FXNewsBean定义 注册到容器中,并将其scope设置为prototype。因为它是有状态的类型,每条新闻都应该是新的独 12 立个体;同时,我们给出MockNewsPersister类,使其实现IFXNewsPersister接口,以模拟注入 FXNewsBean实例后的情况。这样,我们就有了代码清单4-35所展示的类声明和相关配置。

image.png

当多次调用MockNewsPersister的persistNews时,你猜会得到什么结果?如下代码可以帮助我 们揭开答案:

image.png

从输出看,对象实例是相同的,而这与我们的初衷是相悖的。因为每次调用persistNews都会调 用getNewsBean()方法并返回一个FXNewsBean实例,而FXNewsBean实例是prototype类型的,因此每 次不是应该输出不同的对象实例嘛?

好了,问题实际上不是出在FXNewsBean的scope类型是否是prototype的,而是出在实例的取得方 式上面。虽然FXNewsBean拥有prototype类型的scope,但当容器将一个FXNewsBean的实例注入 MockNewsPersister之后,MockNewsPersister就会一直持有这个FXNewsBean实例的引用。虽然每 次输出都调用了getNewsBean()方法并返回了 FXNewsBean 的实例,但实际上每次返回的都是 MockNewsPersister持有的容器第一次注入的实例。这就是问题之所在。换句话说,第一个实例注入 后,MockNewsPersister再也没有重新向容器申请新的实例。所以,容器也不会重新为其注入新的 FXNewsBean类型的实例。

知道原因之后,我们就可以解决这个问题了。解决问题的关键在于保证getNewsBean()方法每次 从容器中取得新的FXNewsBean实例,而不是每次都返回其持有的单一实例。

  1. 方法注入
    Spring容器提出了一种叫做方法注入(Method Injection)的方式,可以帮助我们解决上述问题。 我们所要做的很简单,只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当 该方法被调用的时候,每次返回指定类型的对象实例即可。方法声明需要符合的规格定义如下:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

也就是说,该方法必须能够被子类实现或者覆写,因为容器会为我们要进行方法注入的对象使用 Cglib动态生成一个子类实现,从而替代当前对象。既然我们的getNewsBean()方法已经满足以上方法 声明格式,剩下唯一要做的就是配置该类,配置内容如下所示:


image.png

通过<lookup-method>的name属性指定需要注入的方法名,bean属性指定需要注入的对象,当 getNewsBean方法被调用的时候,容器可以每次返回一个新的FXNewsBean类型的实例。所以,这个时 候,我们再次检查执行结果,输出的实例引用应该是不同的:

persist bean:..domain.FXNewsBean@18aaa1e persist bean:..domain.FXNewsBean@a6aeed
  1. 殊途同归
    除了使用方法注入来达到“每次调用都让容器返回新的对象实例”的目的,还可以使用其他方式达到相同的目的。下面给出其他两种解决类似问题的方法,供读者参考。
  • 使用BeanFactoryAware接口
    我们知道,即使没有方法注入,只要在实现getNewsBean()方法的时候,能够保证每次调用Bean-
    Factory的getBean("newsBean"),就同样可以每次都取得新的FXNewsBean对象实例。现在,我们唯 一需要的,就是让MockNewsPersister拥有一个BeanFactory的引用。

Spring框架提供了一个BeanFactoryAware接口,容器在实例化实现了该接口的bean定义的过程 中,会自动将容器本身注入该bean。这样,该bean就持有了它所处的BeanFactory的引用。BeanFactory- Aware的定义如下代码所示:


image.png

我们让MockNewsPersister实现该接口以持有其所处的BeanFactory的引用,这样MockNews-Persister的定义如代码清单4-36所示。

image.png

配置简化为

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false"> </bean>
<bean id="mockPersister" class="..impl.MockNewsPersister"> </bean>

如此,可以预见到,输出的结果将与我们所预期的相同:
persist bean:..domain.FXNewsBean@121cc40 persist bean:..domain.FXNewsBean@1e893df

  • 使用ObjectFactoryCreatingFactoryBean
    ObjectFactoryCreatingFactoryBean 是 Spring 提 供 的 一 个 FactoryBean 实 现 , 它 返 回 一 个ObjectFactory实例。从ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例可以 为我们返回容器管理的相关对象。实际上,ObjectFactoryCreatingFactoryBean实现了 BeanFactoryAware接口,它返回的ObjectFactory实例只是特定于与Spring容器进行交互的一个实现 而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。

现在,我们使用ObjectFactory取得FXNewsBean的实例,代码清单4-37给出了对应这种方式的 MockNewsPersister实现声明。

image.png

有了以上的类定义之后,我们应该为MockNewsPersister注入相应的ObjectFactory,这也正是 ObjectFactoryCreatingFactoryBean闪亮登场的时候,代码清单4-38给出了对应的配置内容。


image.png
  1. 方法替换
    与方法注入只是通过相应方法为主体对象注入依赖对象不同,方法替换更多体现在方法的实现层 面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑。基本上可以认为,方 法替换可以帮助我们实现简单的方法拦截功能。要知道,我们现在可是在不知不觉中迈上了AOP的大 道哦!

假设某天我看FXNewsProvider不爽,想替换掉它的getAndPersistNews方法默认逻辑,这时, 我就可以用方法替换将它的原有逻辑给替换掉。

首先,我们需要给出org.springframework.beans.factory.support.MethodReplacer的实现类,在这个类中实现将要替换的方法逻辑。假设我们只是简单记录日志,打印简单信息,那么就可以 给出一个类似代码清单4-39所示的MethodReplacer实现类。

image.png

有了要替换的逻辑之后,我们就可以把这个逻辑通过<replaced-method>配置到FXNewsProv- ider的bean定义中,使其生效,配置内容如代码清单4-40所示。


image.png

现在,你猜调用FXNewsProvider的getAndPersistNews方法后,会得到什么结果?输出结果如下所示:

image.png

我们把FXNewsProvider的getAndPersistNews方法逻辑给完全替换掉了。

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