Spring揭秘-BeanFactory的对象注册与依赖绑定方式
BeanFactory的对象注册与依赖绑定方式
BeanFactory作为一个IoC Service Provider,为了能够明确管理各个业务对象以及业务对象之间的依赖绑定关系,同样需要某种途径来记录和管理这些信息。上一章在介绍IoC Service Provider时,我们提到通常会有三种方式来管理这些信息。而BeanFactory几乎支持所有这些方式,很令人兴奋,不是吗?
直接编码方式
其实,把编码方式单独提出来称作一种方式并不十分恰当。因为不管什么方式,最终都需要编码才能“落实”所有信息并付诸使用。不过,通过这些代码,起码可以让我们更加清楚BeanFactory在底层是如何运作的。
下面来看一下我们的FX新闻系统相关类是如何注册并绑定的。
通过编码方式使用BeanFactory实现FX新闻相关类的注册及绑定:
1 |
|
BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理, DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。 DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。基本上, BeanFactory接口只定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理Bean的注册。它们之间的关系如下图所示。
打个比方说, BeanDefinitionRegistry就像图书馆的书架,所有的书是放在书架上的。虽然你还书或者借书都是跟图书馆(也就是BeanFactory,或许BookFactory可能更好些)打交道,但书架才是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“ BookDefinitionRegistry”。
每一个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例。 RootBeanDefinition 和 ChildBeanDefinition是BeanDefinition的两个主要实现类。
现在,我们再来看这段绑定代码,应该就有“柳暗花明”的感觉了。
在main方法中,首先构造一个DefaultListableBeanFactory作为BeanDefinitionRegistry,然后将其交给bindViaCode方法进行具体的对象注册和相关依赖管理,然后通过bindViaCode返回的BeanFactory取得需要的对象,最后执行相应逻辑。在我们的实例里,当然就是取得FXNewsProvider进行新闻的处理。
在bindViaCode方法中,首先针对相应的业务对象构造与其相对应的BeanDefinition,使用了RootBeanDefinition作为BeanDefinition的实现类。构造完成后,将这些BeanDefinition注册到通过方法参数传进来的BeanDefinitionRegistry中。之后,因为我们的FXNewsProvider是采用的构造方法注入,所以,需要通过ConstructorArgumentValues为其注入相关依赖。在这里为了同时说明setter方法注入,也同时展示了在Spring中如何使用代码实现setter方法注入。如果要运行这段代码,需要把setter方法注入部分的4行代码注释掉。最后,以BeanFactory的形式返回已经注册并绑定了所有相关业务对象的BeanDefinitionRegistry实例。
注意:最后一行的强制类型转换是有特定场景的。因为传入的DefaultListableBeanFactory同时实现了BeanFactory和BeanDefinitionRegistry接口,所以,这样做强制类型转换不会出现问题。但需要注意的是,单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的!
外部配置文件方式
Spring的IoC容器支持两种配置文件格式: Properties文件格式和XML文件格式。当然,如果你愿意也可以引入自己的文件格式,前提是真的需要。
采用外部配置文件时, Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后, BeanDefinitionRegistry即完成Bean的注册和加载。
当然,大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinitionReader的相应实现类来做的, BeanDefinitionRegistry只不过负责保管而已。整个过程类似于如下代码:
1 |
|
Properties配置格式的加载
Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinitionReader类用于Properties格式配置文件的加载,所以,我们不用自己去实现BeanDefinitionReader,只要根据该类的读取规则,提供相应的配置文件即可。
对于FXNews系统的业务对象,我们采用如下文件内容(见代码清单4-5)进行配置加载。
1 |
|
这些内容是特定于Spring的PropertiesBeanDefinitionReader的,要了解更多内容,请参照Spring的API参考文档。我们可以很容易地看明白上述代码中的配置内容所要表达的意思。
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而付诸使用了。而这个加载过程实际上也就像我们之前总体上所阐述的那样,如下代码内容再次演示了类似的加载过程。
加载Properties配置的BeanFactory的使用演示:
1 |
|
基于Properties的加载方式就是这么简单,所有的信息配置到Properties文件即可,不用再通过冗长的代码来完成对象的注册和依赖绑定。这些工作就交给相应的BeanDefinitionReader来做吧!哦,我的意思是,让给PropertiesBeanDefinitionReader来做。
注:Spring提供的PropertiesBeanDefinitionReader是按照Spring自己的文件配置规则进行加载的,而同样的道理,你也可以按照自己的规则① 来提供相应的Properties配置文件。只不过,现在需要实现你自己的“ PropertiesBeanDefinitionReader”来读取并解析。这当然有“重新发明轮子”之嫌,不过,如果你只是想试验一下,也可以尝试哦。无非就是按照自己的规则把各个业务对象信息读取后,将编码方式的代码改造一下放到你自己的“ PropertiesBeanDefinitionReader”而已。
XML配置格式的加载
XML配置格式是Spring支持最完整,功能最强大的表达方式。当然,一方面这得益于XML良好的语意表达能力;另一方面, 就是Spring框架从开始就自始至终保持XML配置加载的统一性。同Properties配置加载类似,现在只不过是转而使用XML而已。 Spring 2.x之前,XML配置文件采用DTD( DocumentType Definition)实现文档的格式约束。 2.x之后,引入了基于XSD( XML Schema Definition)的约束方式。不过,原来的基于DTD的方式依然有效,因为从DTD转向XSD只是“形式”上的转变,所以,后面的大部分讲解还会沿用DTD的方式,只有必要时才会给出特殊说明。
如果FX新闻系统对象按照XML配置方式进行加载的话,配置文件内容如以下代码所示。
FX新闻系统相关类对应XML格式的配置内容:
1 |
|
我想这段内容不需要特殊说明吧,应该比Properties文件的内容要更容易理解。如果想知道这些内容背后的更多玄机,往后看吧!
有了XML配置文件,我们需要将其内容加载到相应的BeanFactory实现中,以供使用,如以下代码所示:
1 |
|
与为Properties配置文件格式提供PropertiesBeanDefinitionReader相对应, Spring同样为XML格 式 的 配 置 文 件 提 供 了 现 成 的 BeanDefinitionReader实 现 , 即 XmlBeanDefinitionReader。XmlBeanDefinitionReader负责读取Spring指定格式的XML配置文件并解析,之后将解析后的文件内容映射到相应的BeanDefinition,并加载到相应的BeanDefinitionRegistry中(在这里是DefaultListableBeanFactory)。这时,整个BeanFactory就可以放给客户端使用了。
除了提供XmlBeanDefinitionReader用于XML格式配置文件的加载, Spring还在DefaultListableBeanFactory的基础上构建了简化XML格式配置加载的XmlBeanFactory实现。从以上代码最后注释掉的一行,你可以看到使用了XmlBeanFactory之后,完成XML的加载和BeanFactory的初始化是多么简单。
注:当然,如果你愿意,就像Properties方式可以扩展一样, XML方式的加载同样可以扩展。虽然XmlBeanFactory基本上已经十分完备了,但如果出于某种目的, XmlBeanFactory或者默认的XmlBeanDefinitionReader所使用的XML格式无法满足需要的话,你同样可以通过扩展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对相关类进行标记。
如下代码演示了FXNews相关类使用指定注解标注后的情况:
1 |
|
@Autowired是这里的主角,它的存在将告知Spring容器需要为当前对象注入哪些依赖对象。而@Component则是配合Spring 2.5中新的classpath-scanning功能使用的。现在我们只要再向Spring的配置文件中增加一个“触发器”,使用@Autowired和@Component标注的类就能获得依赖对象的注入了。
如下代码给出的正是针对这部分功能的配置内容:
1 |
|
<context:component-scan/>
会到指定的包( package)下面扫描标注有@Component的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired为这些类注入符合条件的依赖对象。
在以上所有这些工作都完成之后,我们就可以像通常那样加载配置并执行当前应用程序了,如以下代码所示:
1 |
|
本章最后将详细讲解Spring 2.5新引入的“基于注解的依赖注入”。当前的内容只是让我们先从总体上有一个大概的印象,所以,不必强求自己现在就完全理解它们。
注:Google Guice是一个完全基于注解方式、提供依赖注入服务的轻量级依赖注入框架,可以从Google Guice的站点获取有关这个框架的更多信息。