空间站

北极心空

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

3.3.5. 自动装配协作对象

BeanFactory能够自动装配合作bean之间的关系。这就意味着,让Spring通过检查BeanFactory的内容来自动装配你的bean的合作者(也就是其他的bean)。自动装配功能有5种模式。自动装配可以指定给每一个bean,因此可以给一些bean使用而其他的bean不自动装配。通过使用自动装配,可以 减少(或消除)指定属性(或构造函数参数)的需要,显著节省键入工作。 [1] 在XmlBeanFactory中,使用bean元素的autowire属性来指定bean定义的自动装配模式。以下是允许的值.

表 3.2. 自动装配模式

模式 解释
no 不使用自动装配。Bean的引用必须通过ref元素定义。这是默认的配置,在较大的部署环境中不鼓励改变这个配置,因为明确的指定合作者能够得到更多的控制和清晰性。从某种程度上说,这也是系统结构的文档形式。
byName 通过属性名字进行自动装配。这个选项会会检查BeanFactory,查找一个与将要装配的属性同样名字的bean 。比如,你有一个bean的定义被设置为通过名字自动装配,它包含一个master属性(也就是说,它有一个setMaster(...)方法),Spring就会查找一个叫做master的bean定义,然后用它来设置master属性。
byType 如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个这样的bean,就抛出一个致命异常,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check="objects"属性值来指定在这种情况下应该抛出错误。
constructor 这个同byType类似,不过是应用于构造函数的参数。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。
autodetect 通过对bean 检查类的内部来选择constructorbyType。如果找到一个缺省的构造函数,那么就会应用byType。

注意:显式的指定依赖,比如propertyconstructor-arg元素,总会覆盖自动装配。自动装配的行为可以和依赖检查结合使用,依赖检查会在自动装配完成后发生。

注意:正如我们已经提到过的,对于大型的应用,自动装配不鼓励使用,因为它去除了你的合作类的透明性和结构。

3.3.6. 依赖检查

对于部署在BeanFactory的bean的未解决的依赖,Spring有能力去检查它们的存在性。 这些依赖要么是bean的JavaBean式的属性,在bean的定义中并没有为它们设置真实的值, 要么是通过自动装配特性被提供。

当你想确保所有的属性(或者某一特定类型的所有属性)都被设置到bean上面的时候, 这项特性就很有用了。当然,在很多情况下一个bean类的很多属性都会有缺省的值, 或者一些属性并不会应用到所有的应用场景,那么这个特性的作用就有限了 。 依赖检查能够分别对每一个bean应用或取消应用,就像自动装配功能一样。缺省的是 检查依赖关系。 依赖检查可以以几种不同的模式处理。在XmlBeanFactory中, 通过bean定义中的 dependency-check 属性来指定依赖检查,这个属性有以下的值。

表 3.3. 依赖检查模式

模式 解释
none 不进行依赖检查。没有指定值的bean属性仅仅是没有设值。
simple 对基本类型和集合(除了合作者外,比如其他的bean,所有东西)进行依赖检查。
object 对合作者进行依赖检查。
all 对合作者,基本类型和集合都进行依赖检查。

3.4. 自定义bean的本质特征

3.4.1. 生命周期接口

Spring提供了一些标志接口,用来改变BeanFactory中的bean的行为。 它们包括InitializingBeanDisposableBean。 实现这些接口将会导致BeanFactory调用前一个接口的afterPropertiesSet()方法, 调用后一个接口destroy()方法,从而使得bean可以在初始化和析构后做一些特定的动作。

在内部,Spring使用BeanPostProcessors 来处理它能找到的标志接口以及调用适当的方法。 如果你需要自定义的特性或者其他的Spring没有提供的生命周期行为, 你可以实现自己的 BeanPostProcessor。关于这方面更多的内容可以看这里: 第 3.7 节 “使用BeanPostprocessors定制bean”

所有的生命周期的标志接口都在下面叙述。在附录的一节中,你可以找到相应的图, 展示了Spring如何管理bean;那些生命周期的特性如何改变你的bean的本质特征以及它们如何被管理。

3.4.1.1. InitializingBean / init-method

实现org.springframework.beans.factory.InitializingBean 接口允许一个bean在它的所有必须的属性被BeanFactory设置后, 来执行初始化的工作。InitializingBean接口仅仅制定了一个方法:

    * Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).
* <p>This method allows the bean instance to perform initialization only
* possible when all bean properties have been set and to throw an
* exception in the event of misconfiguration.
* @throws Exception in the event of misconfiguration (such
* as failure to set an essential property) or if initialization fails.
*/
void afterPropertiesSet() throws Exception;

注意:通常InitializingBean接口的使用是能够避免的(而且不鼓励,因为没有必要把代码同Spring耦合起来)。Bean的定义支持指定一个普通的初始化方法。在使用XmlBeanFactory的情况下,可以通过指定init-method属性来完成。 举例来说,下面的定义:

<bean   init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}

同下面的完全一样:

<bean  />
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}

但却不把代码耦合于Spring。

3.4.1.2. DisposableBean / destroy-method

实现org.springframework.beans.factory.DisposableBean接口允许一个bean, 可以在包含它的BeanFactory销毁的时候得到一个回调。DisposableBean也只指定了一个方法:

    /**
* Invoked by a BeanFactory on destruction of a singleton.
* @throws Exception in case of shutdown errors.
* Exceptions will get logged but not rethrown to allow
* other beans to release their resources too.
*/
void destroy() throws Exception;

注意:通常DisposableBean接口的使用能够避免的(而且是不鼓励的,因为它不必要地将代码耦合于Spring)。 Bean的定义支持指定一个普通的析构方法。在使用XmlBeanFactory使用的情况下,它是通过 destroy-method属性完成。 举例来说,下面的定义:

<bean   destroy-method="destroy"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like closing connection)
}
}

同下面的完全一样:

<bean  />
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}

但却不把代码耦合于Spring。

重要的提示:当以portotype模式部署一个bean的时候,bean的生命周期将会有少许的变化。 通过定义,Spring无法管理一个non-singleton/prototype bean的整个生命周期, 因为当它创建之后,它被交给客户端而且容器根本不再留意它了。 当说起non-singleton/prototype bean的时候,你可以把Spring的角色想象成“new”操作符的替代品。 从那之后的任何生命周期方面的事情都由客户端来处理。BeanFactory中bean的生命周期将会在第 3.4.1 节 “生命周期接口” 一节中有更详细的叙述 .

3.4.2. 了解自己

3.4.2.1. BeanFactoryAware

对于实现了org.springframework.beans.factory.BeanFactoryAware接口的类, 当它被BeanFactory创建后,它会拥有一个指向创建它的BeanFactory的引用。

public interface BeanFactoryAware {
/**
* Callback that supplies the owning factory to a bean instance.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's afterPropertiesSet or a custom init-method.
* @param beanFactory owning BeanFactory (may not be null).
* The bean can immediately call methods on the factory.
* @throws BeansException in case of initialization errors
* @see BeanInitializationException
*/
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}

这允许bean可以以编程的方式操控创建它们的BeanFactory, 既可以直接使用 org.springframework.beans.factory.BeanFactory接口, 也可以将引用强制将类型转换为已知的子类型从而获得更多的功能。这个特性主要用于编程式地取得其他bean。 虽然在一些场景下这个功能是有用的,但是一般来说它应该避免使用,因为它使代码与Spring耦合在一起, 而且也不遵循反向控制的风格(合作者应当作属性提供给bean)。

3.4.2.2. BeanNameAware

如果一个bean实现了org.springframework.beans.factory.BeanNameAware接口, 并且被部署到一个 BeanFactory中,那么BeanFactory就会通过这个接口来调用bean,以便通知这个bean它被部署的id 。 这个回调发生在普通的bean属性设置之后,在初始化回调之前,比如InitializingBeanafterPropertiesSet方法(或者自定义的init- method)。

3.4.3. FactoryBean

接口org.springframework.beans.factory.FactoryBean 一般由本身是工厂类的对象实现。BeanFactory接口提供了三个方法:

  • Object getObject(): 必须返回一个这个工厂类创建的对象实例。这个实例可以是共享的(取决于这个工厂返回的是singleton还是prototype)。

  • boolean isSingleton(): 如果Factory返回的对象是singleton,返回true,否则返回false

  • Class getObjectType(): 返回getObject()方法返回的对象的类型,如果类型不是预先知道的,则返回null

3.5. 子bean定义

一个bean定义可能会包含大量的配置信息,包括容器相关的信息(比如初始化方法,静态工厂方法名等等)以及构造函数参数和属性的值。一个子bean定义是一个能够从父bean定义继承配置数据的bean定义。 它可以覆盖一些值,或者添加一些其他需要的值。使用父和子的bean定义可以节省很多的输入工作。实际上,这就是一种模版形式。

当以编程的方式使用一个BeanFactory,子bean定义用ChildBeanDefinition类表示。 大多数的用户从来不需要以这个方式使用它们,而是在类似XmlBeanFactory的BeanFactory 中以声明的方式配置bean定义。在一个XmlBeanFactory的bean定义中,使用parent属性指出一个子bean定义,而父bean则作为这个属性的值。

<bean  >
<property ><value>parent</value></property>
<property ><value>1</value></property>
</bean>
<bean
parent="inheritedTestBean" init-method="initialize">
<property ><value>override</value></property>
<!-- age should inherit value of 1 from parent -->
</bean>
bean > <property ><value>parent</value></property> <property ><value>1</value></property> </bean> <bean parent="inheritedTestBeanWithoutClass" init-method="initialize"> <property ><value>override</value></property> <!-- age should inherit value of 1 from parent --> </bean>

这个父bean就无法自己实例化;它实际上仅仅是一个纯模版或抽象bean,充当子定义的父定义。 若要尝试单独使用这样的父bean(比如将它作为其他bean的ref属性而引用,或者直接使用这个父 bean的id调用getBean()方法),将会导致一个错误。同样地,容器内部的preInstantiateSingletons方法会完全忽略这种既没有parent属性也没有class属性的bean定义,因为它们是不完整的。

特别注意:这里并没有办法显式地声明一个bean定义为抽象的。 如果一个bean确实有一个class属性定义,那么它就能够被实例化。而且要注意 XmlBeanFactory默认地将会预实例化所有的singleton的bean。 因此很重要的一点是:如果你有一个(父)bean定义指定了class属性,而你又想仅仅把它当作模板使用, 那么你必须保证将lazy-init属性设置为true(或者将bean标记为non-singleton),否则 XmlBeanFactory(以及其他可能的容器)将会预实例化它。

3.6. BeanFactory之间的交互

BeanFactory本质上不过是高级工厂的接口,它维护不同bean和它们所依赖的bean的注册。 BeanFactory使得你可以利用 bean工厂读取和访问bean定义。 当你使用BeanFactory的时候,你可以象下面一样创建并且读入一些XML格式的bean定义:

InputStream is = new FileInputStream("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);

基本上这就足够了。使用getBean(String)你可以取得你的bean的实例。 如果你将它定义为一个singleton(缺省的)你将会得到同一个bean的引用, 如果你将singleton设置为false,那么你将会每次得到一个新的实例。 在客户端的眼里BeanFactory是惊人的简单。BeanFactory接口仅仅为客户端调用提供了5个方法:

  • boolean containsBean(String): 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true

  • Object getBean(String): 返回一个以所给名字注册的bean的实例。返回一个singleton的共享的实例还是一个新创建的实例, 这取决于bean在BeanFactory配置中如何被配置的。一个BeansException将会在下面两种情况中抛出:bean没有被找到(在这种情况下,抛出的是NoSuchBeanDefinitionException),或者在实例化和准备bean的时候发生异常

  • Object getBean(String,Class): 返回一个以给定名字注册的bean。返回的bean将会被强制类型转换成给定的Class。 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)。 此外getBean(String)的所有规则也同样适用这个方法(同上)

  • boolean isSingleton(String): 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException

  • String[] getAliases(String):如果给定的bean名字在bean定义中有别名,则返回这些别名

3.6.1. 获得一个FactoryBean而不是它生成的bean

有些时候我们需要向BeanFactory请求实际的FactoryBean实例本身, 而不是它生产出来的bean。在调用BeanFactory (包括ApplicationContext)的getBean方法的时候, 在传入的参数bean id前面加一个“&”符号,就可以做到这一点。所以,对于一个id为getBean的FactoryBean, 在BeanFactory上调用getBean("myBean")将会返回FactoryBean的产品,而调用getBean("&myBean")将会返回这个FactoryBean实例本身。

3.7. 使用BeanPostprocessors定制bean

一个bean post-processor是一个实现了org.springframework.beans.factory.config.BeanPostProcessor的类,它包含两个回调方法。当这样的一个类作为BeanFactory的post-processor注册时,对于这个BeanFactory创建的每一个bean实例,在任何初始化方法(afterPropertiesSet和用init-method属性声明的方法)被调用之前和之后,post-processor将会从BeanFactory分别得到一个回调。post-processor可以对这个bean做任何操作事情,包括完全忽略这个回调。一个bean post-processor通常用来检查标记接口,或者做一些诸如将一个bean包装成一个proxy的事情。一些Spring的助手类就是作为bean post-processor而实现的。

有一点很重要,BeanFactory和ApplicationContext对待bean post-processor有少许不同。 一个ApplicationContext会自动监测到任何部署在它之上的实现了 BeanPostProcessor接口的bean, 并且把它们作为post-processor注册,然后factory就会在bean创建的时候恰当地调用它们。 部署一个post-processor同部属一个其他的bean并没有什么区别。而另一方面,当使用普通的BeanFactory的时候, 作为post-processor的bean必须通过类似下面的代码来手动地显式地注册:

ConfigurableBeanFactory bf = new .....;     // create BeanFactory
...                       // now register some beans
// now register any needed BeanPostProcessors
MyBeanPostProcessor pp = new MyBeanPostProcessor();
bf.addBeanPostProcessor(pp);
// now start using the factory
...

因为手工的注册不是很方便,而且ApplicationContext是BeanFactory功能上扩展,所以通常建议当需要post-processor的时候最好使用ApplicationContext。

3.8. 使用BeanFactoryPostprocessors定制bean工厂

一个bean factory post-processor是一个实现了org.springframework.beans.factory.config.BeanFactoryPostProcessor接口的类。它将会被手动执行(BeanFactory的时候)或自动执行(ApplicationContext的时候),在BeanFactory构造出来后, 对整个BeanFactory做某种修改。Spring包含很多已存在的bean factory post-processor, 比如PropertyResourceConfigurerPropertyPlaceHolderConfigurer(这两个将在下面介绍),以及BeanNameAutoProxyCreator对其他bean进行事务包装或者用其他的proxy进行包装,将会在后面叙述。BeanFactoryPostProcessor也能用来添加自定义的editor(可参见第 4.3.2 节 “内建的(PropertyEditors)和类型转换 ”)。

在BeanFactory中,使用BeanFactoryPostProcessor是手动的,将类似于下面:

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
// create placeholderconfigurer to bring in some property
// values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);

ApplicationContext将会检测它所部署的实现了BeanFactoryPostProcessor 接口的bean,然后在适当的时候将它们作为bean factory post-processor而使用。部署这些post-processor与部署其他的bean并没有什么区别。

因为这个手动的步骤并不方便,而且ApplicationContext是BeanFactory的功能扩展,所以当需要使用bean factory post-processor的时候通常建议使用ApplicationContext

3.8.1. PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer作为一个bean factory post-processor实现,可以用来将BeanFactory定义中的属性值放置到另一个单独的Java Properties格式的文件中。 这使得用户不用对BeanFactory的主XML定义文件进行复杂和危险的修改,就可以定制一些基本的属性(比如说数据库的urls,用户名和密码)。

考虑一个BeanFactory定义的片断,里面用占位符定义了DataSource:

在下面这个例子中,定义了一个datasource,并且我们会在一个外部Porperties文件中配置一些相关属性。 在运行时,我们为BeanFactory提供一个PropertyPlaceholderConfigurer,它将用Properties文件中的值替换掉这个datasource的属性值:

<bean   destroy-method="close">
<property ><value>${jdbc.driverClassName}</value></property>
<property ><value>${jdbc.url}</value></property>
<property ><value>${jdbc.username}</value></property>
<property ><value>${jdbc.password}</value></property>
</bean>

真正的值来自于另一个Properties格式的文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

如果要在BeanFactory中使用,bean factory post-processor必须手动运行:

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
cfg.postProcessBeanFactory(factory);

注意,ApplicationContext能够自动辨认和应用在其上部署的实现了BeanFactoryPostProcessor的bean。这就意味着,当使用ApplicationContext的时候应用PropertyPlaceholderConfigurer会非常的方便。由于这个原因,建议想要使用这个或者其他bean factory postprocessor的用户使用ApplicationContext代替BeanFactroy。

PropertyPlaceHolderConfigurer不仅仅在你指定的Porperties文件中查找属性, 如果它在其中没有找到你想使用的属性,它还会在Java的系统properties中查找。 这个行为能够通过设置配置中的systemPropertiesMode 属性来定制。这个属性有三个值, 一个让配置总是覆盖,一个让它永不覆盖,一个让它仅在properties文件中找不到的时候覆盖。 请参考 PropertiesPlaceholderConfigurer的JavaDoc获得更多信息。

3.8.2.  PropertyOverrideConfigurer

另一个bean factory post-processor,PropertyOverrideConfigurer,类似于PropertyPlaceholderConfigurer,但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。如果起覆盖作用的 Properties文件没有某个bean属性的内容,那么缺省的上下文定义将被使用。

注意:bean 工厂的定义并 不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻明显地知道覆盖配置是否被使用了。在有多个PorpertyOverrideConfigurer对用一个bean属性定义了不同的值的时候,最后一个将取胜(取决于覆盖的机制)。

Properties文件的一行配置应该是如下的格式:

beanName.property=value

一个properties文件的例子将会是下面这样的:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

这个例子可以用在包含dataSource的bean的BeanFactory中,这个bean有driverurl属性。

3.9. 注册附加的定制PropertyEditor

当用字符串值设置bean的属性时,BeanFactory实质上使用了标准的JavaBeans 的PropertyEditor将这些String转换为属性的复杂类型。Spring预先注册了很多定制的PropertyEditor(比如,将一个字符串表示的classname转换成真正的Class对象)。另外,一个类的PropertyEditor如果被恰当地命名并且放在与它提供支持的类同一个包内,那么Java标准的JavaBeans PropertyEditor查找机制就会自动找到这个PropertyEditor。

如果需要注册其他定制的PropertyEditor,这里也有几个可用的机制。

最手动的方法,也是通常不方便和不推荐的,就是简单地使用ConfigurableBeanFactory接口的registerCustomEditor()方法,假定你已经有一个BeanFactory引用。

比较方便的机制是,使用一个特殊的叫CustomEditorConfigurer的bean factory post-precessor。尽管bean factory post-processor能够和BeanFactory半手动地使用,但是它有一个嵌套的属性设置。所以强烈建议,就象这里描述的,它能够和ApplicationContxt一起使用,这样它就可以以其它bean的方式被部署,并且被自动监测和应用。

3.10. 介绍ApplicationContext

beans包提供了以编程的方式管理和操控bean的基本功能,而context包增加了ApplicationContext,它以一种更加面向框架的方式增强了BeanFactory的功能。多数用户可以以一种完全的声明式方式来使用ApplicationContext,甚至不用去手动创建它,但是却去依赖象ContextLoader的支持类,在J2EE的的web应用的启动进程中用它启动ApplicationContext。当然,这种情况下还是可以以编程的方式创建一个ApplicationContext。

Context包的基础是位于org.springframework.context包中的ApplicationContext接口。它是由BeanFactory接口集成而来,提供BeanFactory所有的功能。为了以一种更向面向框架的方式工作,context包使用分层和有继承关系的上下文类,包括:

  • MessageSource, 提供对i18n消息的访问

  • 资源访问, 比如URL和文件

  • 事件传递给实现了ApplicationListener接口的bean

  • 载入多个(有继承关系)上下文类,使得每一个上下文类都专注于一个特定的层次,比如应用的web层

因为ApplicationContext包括了BeanFactory所有的功能,所以通常建议先于BeanFactory使用,除了有限的一些场合比如在一个Applet中,内存的消耗是关键的,每kb字节都很重要。接下来的章节将叙述ApplicationContext在BeanFactory的基本能力上增建的功能。

3.11. ApplicationContext中增加的功能

正如上面说明的那样,ApplicationContext有几个区别于BeanFactory的特性。让我们一个一个地讨论它们。

3.11.1. 使用MessageSource

ApplicationContext接口继承MessageSource接口,所以提供了messaging功能(i18n或者国际化)。同NestingMessageSource一起使用,就能够处理分级的信息,这些是Spring提供的处理信息的基本接口。让我们很快浏览一下这里定义的方法:

  • String getMessage (String code, Object[] args, String default, Locale loc):这个方法是从MessageSource取得信息的基本方法。如果对于指定的locale没有找到信息,则使用默认的信息。传入的参数args被用来代替信息中的占位符,这个是通过Java标准类库的MessageFormat实现的。

  • String getMessage (String code, Object[] args, Locale loc):本质上和上一个方法是一样的,除了一点区别:没有默认值可以指定;如果信息找不到,就会抛出一个NoSuchMessageException

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):上面两个方法使用的所有属性都是封装到一个叫做MessageSourceResolvable的类中,你可以通过这个方法直接使用它。

当ApplicationContext被加载的时候,它会自动查找在context中定义的MessageSource bean。这个bean必须叫做messageSource。如果找到了这样的一个bean,所有对上述方法的调用将会被委托给找到的message source。如果没有找到message source,ApplicationContext将会尝试查它的父亲是否包含这个名字的bean。如果有,它将会把找到的bean作为MessageSource。如果它最终没有找到任何的信息源,一个空的StaticMessageSource将会被实例化,使它能够接受上述方法的调用。

Spring目前提供了两个MessageSource的实现。它们是ResourceBundleMessageSourceStaticMessageSource。两个都实现了NestingMessageSource以便能够嵌套地解析信息。StaticMessageSource很少被使用,但是它提供以编程的方式向source增加信息。ResourceBundleMessageSource用得更多一些,我们将提供它的一个例子:

<beans>
<bean
>
<property >
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans> 

这段配置假定你在classpath有三个resource bundle,分别叫做fformatexceptionswindows。使用JDK通过ResourceBundle解析信息的标准方式,任何解析信息的请求都会被处理。TODO:举一个例子

3.11.2. 事件传递

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口来提供的。如果上下文中部署了一个实现了ApplicationListener接口的bean,每次一个ApplicationEvent发布到ApplicationContext时,那个bean就会被通知。实质上,这是标准的Observer设计模式。Spring提供了三个标准事件:

表 3.4. 内置事件

事件 解释
ContextRefreshedEvent 当ApplicationContext已经初始化或刷新后发送的事件。这里初始化意味着:所有的bean被装载,singleton被预实例化,以及ApplicationContext已准备好
ContextClosedEvent 当使用ApplicationContext的close()方法结束上下文的时候发送的事件。这里结束意味着:singleton被销毁
RequestHandledEvent 一个与web相关的事件,告诉所有的bean一个HTTP请求已经被响应了(这个事件将会在一个请求结束被发送)。注意,这个事件只能应用于使用了Spring的DispatcherServlet的web应用

同样也可以实现自定义的事件。通过调用ApplicationContext的publishEvent()方法,并且指定一个参数,这个参数是你自定义的事件类的一个实例。我们来看一个例子。首先是ApplicationContext:

<bean  >
<property >
<list>
<value>black@list.org</value>
<value>white@list.org</value>
<value>john@doe.org</value>
</list>
</property>
</bean>
<bean  >
<property >
<value>spam@list.org</value>
</property>
</bean>

然后是实际的bean:

public class EmailBean implements ApplicationContextAware {
/** the blacklist */
private List blackList;
public void setBlackList(List blackList) {
this.blackList = blackList;
}
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent evt = new BlackListEvent(address, text);
ctx.publishEvent(evt);
return;
}
// send email
}
}
public class BlackListNotifier implement ApplicationListener {
/** notification address */
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(ApplicationEvent evt) {
if (evt instanceof BlackListEvent) {
// notify appropriate person
}
}
}

当然,这个特定的例子或许可以用更好的方式实现(或许使用AOP特性),但是它用来说明基本的事件机制是足够了。

3.11.3. 在Spring中使用资源

很多应用程序都需要访问资源。资源可以包括文件,以及象web页面或NNTP newsfeeds的东西。Spring提供了一个清晰透明的方案,以一种协议无关的方式访问资源。ApplicationContext接口包含一个方法(getResource(String))负责这项工作。

Resource类定义了几个方法,这几个方法被所有的Resource实现所共享:

表 3.5. 资源功能

方法 解释
getInputStream() 用InputStream打开资源,并返回这个InputStream。
exists() 检查资源是否存在,如果不存在返回false
isOpen() 如果这个资源不能打开多个流将会返回true。因为除了基于文件的资源,一些资源不能被同事多次读取,它们就会返回false。
getDescription() 返回资源的描述,通常是全限定文件名或者实际的URL。

Spring提供了几个Resource的实现。它们都需要一个String表示的资源的实际位置。依据这个String,Spring将会自动为你选择正确的Resource实现。当向ApplicationContext请求一个资源时,Spring首先检查你指定的资源位置,寻找任何前缀。根据不同的ApplicationContext的实现,不同的Resource实现可被使用的。Resource最好是使用ResourceEditor来配置,比如XmlBeanFactory。

3.12. 在ApplicationContext中定制行为

BeanFactory已经提供了许多机制来控制部署在其中的bean的生命周期(比如象InitializingBeanDisposableBean的标志接口,它们的配置效果和XmlBeanFactory配置中的init-methoddestroy-method属性以及bean post-processor是相同的。在ApplicationContext中,这些也同样可以工作,但同时也增加了一些用于定制bean和容器行为的机制。

3.12.1. ApplicationContextAware标记接口

所有BeanFactory中可用的标志接口这里也可以使用。ApplicationContext又增加了一个bean可以实现的标志接口:org.springframework.context.ApplicationContextAware。如果一个bean实现了这个接口并且被部署到了context中,当这个bean创建的时候,将使用这个接口的setApplicationContext()方法回调这个bean,为这个bean提供一个指向当前上下文的引用,这个引用将被存储起来以便bean以后与上下文发生交互。

3.12.2. BeanPostProcessor

Bean post-processor(实现了org.springframework.beans.factory.config.BeanPostProcessor的java类)在上面已经叙述过了。还是值得再次提到,post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一个ApplicationContext中,一个实现了上述标记接口的bean将会被自动查找,并且作为一个bean post-processor被注册,在创建工厂中的每一个bean时都会被适当地调用。

3.12.3. BeanFactoryPostProcessor

Bean factory post-processor(实现了org.springframework.beans.factory.config.BeanFactoryPostProcessor接口的java类)在前面已经介绍过。这里也值得再提一下,bean factory post-processor在ApplicationContext中使用也要比在普通的BeanFactory中使用方便得多。在一个ApplicationContext中,一个实现了上述标记接口的bean将会被自动查找,并且作为一个bean factory post-processor被注册,在适当的时候被调用。

3.12.4. PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer在BeanFactory中的使用已经叙述过了。这里仍然值得再次提一下,它在ApplicationContext中的使用要更加方便一些,因为当它像其它bean一样部署的时候,上下文会自动识别和应用任何的bean factory post-processor,比如这个。这时候就没有必要手动地运行它。

<!-- property placeholder post-processor -->
<bean
>
<property ><value>jdbc.properties</value></property>
</bean>

3.13. 注册附加的定制PropertyEditors

正如前面曾提到的,Spring使用标准的JavaBeans的PropertyEditor将字符串形式表示的属性值转换为属性真实的复杂的类型。CustomEditorConfigurer,这个bean factory post-processor可以使ApplicationContext很方便地增加对额外的PropertyEditor的支持。

考虑一个用户类ExoticType,以及另外一个需要ExoticType作为属性的DependsOnExoticType类:

public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}

当设置好的时候,我们希望能够以字符串的形式指定属性,同时背后的PropertyEditor会将这个字符串转换为一个真正的Exotic类型的对象:

<bean  >
<property ><value>aNameForExoticType</value></property>
</bean>

而这个PorpertyEditor看起来象下面这样:

// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {
private String format;
public void setFormat(String format) {
this.format = format;
}
public void setAsText(String text) {
if (format != null && format.equals("upperCase")) {
text = text.toUpperCase();
}
ExoticType type = new ExoticType(text);
setValue(type);
}
}

最后,我们用CustomEditorConfigurer将新的PropertyEditor注册到ApplicationContext上,然后ApplicationContext就可以在需要的时候使用这个PropertyEditor了:

<bean
>
<property >
<map>
<entry key="example.ExoticType">
<bean >
<property >
<value>upperCase</value>
</property>
</bean>
</entry>
</map>
</property>
</bean>

3.14. 用方法调用的返回值来设置bean的属性

有些时候,需要用容器中另一个bean的方法返回值来设置一个bean的属性,或者使用其它任意类(不一定是容器中的bean)的静态方法的返回值来设置。此外,有些时候,需要调用一个静态或非静态的方法来执行某些初始化工作。对于这两个目的,可以使用MethodInvokingFactoryBean助手类。它是一个FactoryBean,可以返回一个静态或非静态方法的调用结果。

下面是一个基于XML的BeanFactory的bean定义的例子,它使用那个助手类调用静态工厂方法:

<bean  >
<property ><value>com.whatever.MyClassFactory.getInstance</value></property>
</bean>

下面这个例子先调用一个静态方法,然后调用一个实例方法,来获得一个Java System的属性。虽然有点罗嗦,但是可以工作:

<bean  >
<property ><value>java.lang.System</value></property>
<property ><value>getProperties</value></property>
</bean>
<bean  >
<property ><ref local="sysProps"/></property>
<property ><value>getProperty</value></property>
<property >
<list>
<value>java.version</value>
</list>
</property>
</bean>

注意,实际上这个类多半用来访问工厂方法,所以MethodInvokingFactoryBean默认以singleton方式进行操作。经由容器的第一次请求工厂生成对象将会引起调用特定的工厂方法,它的返回值将会被缓存并且返回供这次请求和以后的请求使用。这个工厂的一个内部singleton属性可以被设置为false,从而导致每次对象的请求都会调用那个目标方法。

通过设置targetMethod属性为一个静态方法名的字符串来指定静态目标方法,而设置targetClass为定义静态方法的类。或者,通过设置targetObject属性目标对象,设置targetMethod属性要在目标对象上调用的方法名,来指定目标实例方法。方法调用的参数可以通过设置args属性来指定。

3.15. 从一个web应用创建ApplicationContext

与BeanFactory总是以编程的方式创建相反,ApplicationContext可以通过使用比如ContextLoader声明式地被创建。当然你也可以用ApplicationContext的任一种实现来以编程的方式创建它。首先,我们来看看ContextLoader以及它的实现。

ContextLoader有两个实现:ContextLoaderListenerContextLoaderServlet。它们两个有着同样的功能,除了listener不能在Servlet 2.2兼容的容器中使用。自从Servelt 2.4规范,listener被要求在web应用启动后初始化。很多2.3兼容的容器已经实现了这个特性。使用哪一个取决于你自己,但是如果所有的条件都一样,你大概会更喜欢ContextLoaderListener;关于兼容方面的更多信息可以参照ContextLoaderServlet的JavaDoc。

你可以象下面这样用ContextLoaderListener注册一个ApplicationContext:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- OR USE THE CONTEXTLOADERSERVLET INSTEAD OF THE LISTENER
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->

这个listener需要检查contextConfigLocation参数。如果不存在的话,它将默认使用/WEB-INF/applicationContext.xml。如果它存在,它就会用预先定义的分隔符(逗号,分号和空格)分开分割字符串,并将这些值作为应用上下文将要搜索的位置。ContextLoaderServlet可以用来替换ContextLoaderListener。这个servlet像listener那样使用contextConfigLocation参数。

3.16. 粘合代码和罪恶的singleton

一个应用中的大多数代码最好写成依赖注射(反向控制)的风格,这样这些代码就和BeanFactory容器或者ApplicationContext容器无关,它们在被创建的时候从容器处得到自己的依赖,并且完全不知道容器的存在。然而,对于少量需要与其它代码粘合的粘合层代码来说,有时候就需要以一种singleton(或者类似singleton)的方式来访问BeanFactory或ApplicationContext。举例来说,第三方的代码可能想要(以Class.forName()的方式)直接构造一个新的对象,却没办法从BeanFactory中得到这些对象。如果第三方代码构造的对象只是一个小的stub或proxy,并且使用singleton方式访问BeanFactroy/ApplicationContext来获得真正的对象,大多数的代码(由BeanFactory产生的对象)仍然可以使用反向控制。因此大多数的代码依然不需要知道容器的存在,或者容器是如何被访问的,并保持和其它代码解耦,有着所有该有的益处。EJB也可以使用这种stub/proxy方案代理到一个普通的BeanFactory产生的java实现的对象。虽然理想情况下BeanFactory不需要是一个singleton,但是如果每个bean使用它自己的non-singleton的BeanFactory,对于内存使用或初始化次数都是不切实际。

另一个例子,在一个多层的复杂的J2EE应用中(比如有很多JAR,EJB,以及WAR打包成一个EAR),每一层都有自己的ApplicationContext定义(有效地组成一个层次结构),如果顶层只有一个web-app(WAR)的话,比较好的做法是创建一个由不同层的XML定义文件组成的组合ApplicationContext。所有的ApplicationContext变体都可以从多个定义文件以这种方式构造出来。但是,如果在顶层有多个兄弟web-apps,为每一个web-app创建一个ApplicationContext,但是每个ApplicationContext都包含大部分相同的底层的bean定义,这就会因为内存使用而产生问题,因为创建bean的多个copy会花很长时间初始化(比如Hibernate SessionFactory),以及其它可能产生的副作用。作为替换,诸如ContextSingletonBeanFactoryLocatorSingletonBeanFactoryLocator的类可以在需要的时候以有效的singleton方式,加载多个层次的(比如一个是另一个的父亲)BeanFactory或ApplicationContext,这些将会作为web-app ApplicationContext的parents。这样做的结果就是,底层的bean定义只在需要的时候加载并且只被加载一次。

3.16.1. 使用SingletonBeanFactoryLocator和ContextSingletonBeanFactoryLocator

你可以查看SingletonBeanFactoryLocatorContextSingletonBeanFactoryLocator的JavaDoc来获得详细的使用例子。

正如在EJB那一章提到的,Spring为EJB提供的方便的基类一般使用一个non-singleton的BeanFactoryLocator实现,这个可以在需要的时候被SingletonBeanFactoryLocatorContextSingletonBeanFactoryLocator替换。

posted on 2008-07-17 10:34 芦苇 阅读(552) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航: