第 3 章 Beans, BeanFactory和ApplicationContext

3.1. 简介

在Spring中,两个最基本最重要的包是 org.springframework.beansorg.springframework.context. 这两个包中的代码为Spring的反向控制 特性(也叫作依赖注射)提供了基础。 BeanFactory提供了一种先进的配置机制来管理任何种类bean(对象),这种配置机制考虑到任何一种可能的存储方式。 ApplicationContext建立在BeanFactory之上,并增加了其他的功能,比如更容易同Spring AOP特性整合, 消息资源处理(用于国际化),事件传递,以声明的方式创建ApplicationContext, 可选的父上下文和与应用层相关的上下文(比如WebApplicationContext),以及其他方面的增强。

简而言之,BeanFactory提供了配置框架和基本的功能, 而 ApplicationContext为它增加了更强的功能,这些功能中的一些或许更加接近J2EE并且围绕企业级应用。一般来说,ApplicationContext是BeanFactory的完全超集, 任何BeanFactory功能和行为的描述也同样被认为适用于ApplicationContext

用户有时不能确定BeanFactory和ApplicationContext中哪一个在特定场合下更适合。 通常大部分在J2EE环境的应用中,最好选择使用ApplicationContext, 因为它不仅提供了BeanFactory所有的特性以及它自己附加的特性,而且还提供以声明的方式使用一些功能, 这通常是令人满意的。BeanFactory主要是在非常关注内存使用的情况下 (比如在一个每kb都要计算的applet中)使用,而且你也不需要用到ApplicationContext的所有特性。

这一章粗略地分为两部分,第一部分包括对BeanFactory和ApplicationContext都适用的一些基本原则。第二部分包括仅仅适用于ApplicationContext的一些特性

3.2. BeanFactory 和 BeanDefinitions - 基础

3.2.1. BeanFactory

BeanFactory实际上是实例化,配置和管理众多bean的容器。 这些bean通常会彼此合作,因而它们之间会产生依赖。 BeanFactory使用的配置数据可以反映这些依赖关系中 (一些依赖可能不像配置数据一样可见,而是在运行期作为bean之间程序交互的函数)。

一个BeanFactory可以用接口org.springframework.beans.factory.BeanFactory表示, 这个接口有多个实现。 最常使用的的简单的eanFactory实现是org.springframework.beans.factory.xml.XmlBeanFactory。 (这里提醒一下:ApplicationContext是BeanFactory的子类, 所以大多数的用户更喜欢使用ApplicationContext的XML形式)。

虽然大多数情况下,几乎所有被BeanFactory管理的用户代码都不需要知道BeanFactory, 但是BeanFactory还是以某种方式实例化。可以使用下面的代码实例化BeanFactory:

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

或者

ClassPathResource res = new ClassPathResource("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(res);

或者

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
        new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
// of course, an ApplicationContext is just a BeanFactory
BeanFactory factory = (BeanFactory) appContext;

很多情况下,用户代码不需要实例化BeanFactory, 因为Spring框架代码会做这件事。例如,web层提供支持代码,在J2EE web应用启动过程中自动载入一个Spring ApplicationContext。这个声明过程在这里描述:

编程操作BeanFactory将会在后面提到,下面部分将集中描述BeanFactory的配置.

一个最基本的BeanFactory配置由一个或多个它所管理的Bean定义组成。在一个XmlBeanFactory中,根节点beans中包含一个或多个bean元素。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  
  <bean id="..." class="...">
    ...
  </bean>
  <bean id="..." class="...">
    ...
  </bean>

  ...

</beans>

3.2.2. BeanDefinition

一个XmlBeanFactory中的Bean定义包括的内容有:

  • classname:这通常是bean的真正的实现类。但是如果一个bean使用一个静态工厂方法所创建而不是被普通的构造函数创建,那么这实际上就是工厂类的classname

  • bean行为配置元素:它声明这个bean在容器的行为方式(比如prototype或singleton,自动装配模式,依赖检查模式,初始化和析构方法)

  • 构造函数的参数和新创建bean需要的属性:举一个例子,一个管理连接池的bean使用的连接数目(即可以指定为一个属性,也可以作为一个构造函数参数),或者池的大小限制

  • 和这个bean工作相关的其他bean:比如它的合作者(同样可以作为属性或者构造函数的参数)。这个也被叫做依赖。

上面列出的概念直接转化为组成bean定义的一组元素。这些元素在下面的表格中列出,它们每一个都有更详细的说明的链接。

注意bean定义可以表示为真正的接口org.springframework.beans.factory.config.BeanDefinition以及它的各种子接口和实现。然而,绝大多数的用户代码不需要与BeanDefination直接接触。

3.2.3. bean的类

class属性通常是强制性的(参考第 3.2.3.3 节 “通过实例工厂方法创建bean”第 3.5 节 “子bean定义”),有两种用法。在绝大多数情况下,BeanFactory直接调用bean的构造函数来"new"一个bean(相当于调用new的Java代码),class属性指定了需要创建的bean的类。 在比较少的情况下,BeanFactory调用某个类的静态的工厂方法来创建bean, class属性指定了实际包含静态工厂方法的那个类。 (至于静态工厂方法返回的bean的类型是同一个类还是完全不同的另一个类,这并不重要)。

3.2.3.1. 通过构造函数创建bean

当使用构造函数创建bean时,所有普通的类都可以被Spring使用并且和Spring兼容。 这就是说,被创建的类不需要实现任何特定的接口或者按照特定的样式进行编写。仅仅指定bean的类就足够了。 然而,根据bean使用的IoC类型,你可能需要一个默认的(空的)构造函数。

另外,BeanFactory并不局限于管理真正的JavaBean,它也能管理任何你想让它管理的类。 虽然很多使用Spring的人喜欢在BeanFactory中用真正的JavaBean (仅包含一个默认的(无参数的)构造函数,在属性后面定义相对应的setter和getter方法),但是在你的BeanFactory中也可以使用特殊的非bean样式的类。 举例来说,如果你需要使用一个遗留下来的完全没有遵守JavaBean规范的连接池, 不要担心,Spring同样能够管理它。

使用XmlBeanFactory你可以像下面这样定义你的bean class:

<bean id="exampleBean"
      class="examples.ExampleBean"/>
<bean name="anotherExample"
      class="examples.ExampleBeanTwo"/> 

至于为构造函数提供(可选的)参数,以及对象实例创建后设置实例属性,将会在后面叙述

3.2.3.2. 通过静态工厂方法创建Bean

当你定义一个使用静态工厂方法创建的bean,同时使用class属性指定包含静态工厂方法的类,这个时候需要factory-method属性来指定工厂方法名。Spring调用这个方法(包含一组可选的参数)并返回一个有效的对象,之后这个对象就完全和构造方法创建的对象一样。用户可以使用这样的bean定义在遗留代码中调用静态工厂。

下面是一个bean定义的例子,声明这个bean要通过factory-method指定的方法创建。注意这个bean定义并没有指定返回对象的类型,只指定包含工厂方法的类。在这个例子中,createInstance 必须是static方法 .

<bean id="exampleBean"
      class="examples.ExampleBean2"
      factory-method="createInstance"/>

至于为工厂方法提供(可选的)参数,以及对象实例被工厂方法创建后设置实例属性,将会在后面叙述.

3.2.3.3. 通过实例工厂方法创建bean

使用一个实例工厂方法(非静态的)创建bean和使用静态工厂方法非常类似,调用一个已存在的bean(这个bean应该是工厂类型)的工厂方法来创建新的bean。

使用这种机制,class属性必须为空,而且factory-bean属性必须指定一个bean的名字,这个bean一定要在当前的bean工厂或者父bean工厂中,并包含工厂方法。 而工厂方法本身仍然要通过factory-method属性设置。

下面是一个例子:

<!-- The factory bean, which contains a method called
     createInstance -->
<bean id="myFactoryBean"
      class="...">
  ...
</bean>
<!-- The bean to be created via the factory bean -->
<bean id="exampleBean"
      factory-bean="myFactoryBean"
      factory-method="createInstance"/>

虽然我们要在后面讨论设置bean的属性,但是这个方法意味着工厂bean本身能够被容器通过依赖注射来管理和配置

3.2.4. Bean的标志符 (idname)

每一个bean都有一个或多个id(也叫作标志符,或名字;这些名词说的是一回事)。这些id在管理bean的BeanFactory或ApplicationContext中必须是唯一的。 一个bean差不多总是只有一个id,但是如果一个bean有超过一个的id,那么另外的那些本质上可以认为是别名。

在一个XmlBeanFactory中(包括ApplicationContext的形式), 你可以用id或者name属性来指定bean的id(s),并且在这两个或其中一个属性中至少指定一个id。 id属性允许你指定一个id,并且它在XML DTD(定义文档)中作为一个真正的XML元素的ID属性被标记, 所以XML解析器能够在其他元素指回向它的时候做一些额外的校验。正因如此,用id属性指定bean的id是一个比较好的方式。 然而,XML规范严格限定了在XML ID中合法的字符。通常这并不是真正限制你, 但是如果你有必要使用这些字符(在ID中的非法字符),或者你想给bean增加其他的别名, 那么你可以通过name属性指定一个或多个id(用逗号,或者分号;分隔)。

3.2.5. Singleton的使用与否

Beans被定义为两种部署模式中的一种:singleton或non-singleton。 (后一种也别叫作prototype,尽管这个名词用的不精确因为它并不是非常适合)。 如果一个bean是singleton形态的,那么就只有一个共享的实例存在, 所有和这个bean定义的id符合的bean请求都会返回这个唯一的、特定的实例。

如果bean以non-singleton,prototype模式部署的话,对这个bean的每次请求都会创建一个新的bean实例。这对于例如每个user需要一个独立的user对象这样的情况是非常理想的。

Beans默认被部署为singleton模式,除非你指定。要记住把部署模式变为non-singletion(prototype)后,每一次对这个bean的请求都会导致一个新创建的bean,而这可能并不是你真正想要的。所以仅仅在绝对需要的时候才把模式改成prototype。

在下面这个例子中,两个bean一个被定义为singleton,而另一个被定义为non-singleton(prototype)。客户端每次向BeanFactory请求都会创建新的exampleBean,而AnotherExample仅仅被创建一次;在每次对它请求都会返回这个实例的引用。

<bean id="exampleBean"
      class="examples.ExampleBean" singleton="false"/>
<bean name="yetAnotherExample"
      class="examples.ExampleBeanTwo" singleton="true"/>

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

3.3. 属性,合作者,自动装配和依赖检查

3.3.1. 设置bean的属性和合作者

反向控制通常与依赖注入同时提及。基本的规则是bean通过以下方式来定义它们的依赖(比如它们与之合作的其他对象):构造函数的参数,工厂方法的参数; 当对象实例被构造出来或从一个工厂方法返回后设置在这个实例上的属性。容器的工作就是创建完bean之后,真正地注入这些依赖。这完全是和一般控制方式相反的(因此称为反向控制),比如bean实例化,或者直接使用构造函数定位依赖关系,或者类似Service Locator模式的东西。我们不会详细阐述依赖注射的优点,很显然通过使用它:代码变得非常清晰;当bean不再自己查找他们依赖的类而是由容器提供,甚至不需要知道这些类在哪里以及它们实际上是什么类型,这时高层次的解耦也变得很容易了。

正如上面提到的那样,反向控制/依赖注射存在两种主要的形式:

  • 基于setter的依赖注射,是在调用无参的构造函数或无参的静态工厂方法实例化你的bean之后, 通过调用你的bean上的setter方法实现的。 在BeanFactory中定义的使用基于setter方法的注射依赖的bean是真正的JavaBean。 Spring一般提倡使用基于setter方法的依赖注射,因为很多的构造函数参数将会是笨重的, 尤其在有些属性是可选的情况下。

  • 基于构造函数的依赖注射,它是通过调用带有许多参数的构造方法实现的, 每个参数表示一个合作者或者属性。 另外,调用带有特定参数的静态工厂方法来构造bean可以被认为差不多等同的, 接下来的文字会把构造函数的参数看成和静态工厂方法的参数类似。 虽然Spring一般提倡在大多数情况下使用基于setter的依赖注射, 但是Spring还是完全支持基于构造函数的依赖注射, 因为你可能想要在那些只提供多参数构造函数并且没有setter方法的遗留的bean上使用Spring。 另外对于一些比较简单的bean,一些人更喜欢使用构造函数方法以确保bean不会处于错误的状态。

BeanFactory同时支持这两种方式将依赖注射到被管理bean中。(实际上它还支持在一些依赖已经通过构造函数方法注射后再使用setter方法注射依赖)。依赖的配置是以BeanDefinition的形式出现,它和JavaBeans的PropertyEditors一起使用从而知道如何把属性从一个格式转变为另一个。真正传送的值被封装为PropertyValue对象。然而,大多数Spring的使用者并不要直接(比如编程的方式)处理这些类,而更多地使用一个XML定义文件,这个文件会在内部被转变为这些类的实例,用来读取整个BeanFactory或ApplicationContext。

Bean依赖的决定通常取决于下面这些内容:

  1. BeanFactory通过使用一个描述所有bean的配置被创建和实例化。大多数的Spring用户使用一个支持XML格式配置文件的BeanFactory或ApplicationContext实现。

  2. 每一个bean的依赖表现为属性,构造函数参数,或者当用静态工厂方法代替普通构造函数时工厂方法的参数。这些依赖将会在bean真正被创建出来后提供给bean。

  3. 每一个属性或者构造函数参数要么是一个要被设置的值的定义,要么是一个指向BeanFactory中其他bean的引用。在ApplicationContext的情况下,这个引用可以指向一个父亲ApplicationContext中bean。

  4. 每一个属性或构造函数参数的值,必须能够从(配置文件中)被指定的格式转变为真实类型。缺省情况下,Spring能够把一个字符串格式的值转变为所有内建的类型,比如int, longString, boolean等等。另外当说到基于XML的BeanFactory实现的时候(包括ApplicationContext实现),它们已经为定义Lists, Maps, Sets和Properties集合类型提供了内在的支持。另外,Spring通过使用JavaBeans的 PropertyEditor定义,能够将字符串值转变为其他任意的类型。(你可以为PropertyEditor提供你自己的PropertyEditor定义从而能够转变你自定义的类型;更多关于PropertyEditors的信息以及如何手工增加自定义的PropertyEditors请参看第 3.9 节 “注册附加的定制PropertyEditor”)。当一个bean属性是一个Java Class类型,Spring允许你用这个类的名字的字符串作为这个属性的值,ClassEditor 这个内建的PropertyEditor会帮你把类的名字转变成真实的Class实例。

  5. 很重要的一点就是:Spring在BeanFactory创建的时候要校验BeanFactory中每一个Bean的配置。这些校验包括作为Bean引用的属性必须实际引用一个合法的bean(比如被引用的bean也定义在BeanFactory中,或者当ApplicationContext时,在父亲ApplicationContext中)。但是,bean属性本身直到bean被真实建立的的时候才被设置。对于那些是singleton并且被设置为pre-instantiated的bean来说(比如一个ApplicationContext中的singletonbean),bean在创建BeanFactory的时候创建,但是对于其他情况,发生在bean被请求的时候。当一个bean必须被创建时,它会潜在地导致一系列的其他bean被创建,像它的依赖以及它的依赖的依赖(如此下去)被创建和赋值。

  6. 通常你可以信任Spring做了正确的事情。它会在BeanFactory装载的时候检查出错误,包括对不存在bean的引用和循环引用。它会尽可能晚地设置属性和解决依赖(比如创建那些需要的依赖),也就是在bean真正被创建的时候。这就意味着:就算一个BeanFactory被正确地装载,稍后当你请求一个bean的时候,如果创建那个bean或者它的依赖的时候出现了错误,这个BeanFactory也会抛出一个异常。比如,如果一个bean抛出一个异常作为缺少或非法属性的结果,这样的情况就会发生。这种潜在地推迟一些配置错误可见性的行为正是ApplicationContext默认预实例化singleton bean的原因。以前期的时间和内存为代价在beans真正需要之前创建它们,你就可以在ApplicationContext创建的时候找出配置错误,而不是在后来。如果你愿意,你也可以覆盖这种默认的行为,设置这些singleton bean为lazy-load(不是预实例化的)。

几个例子:

首先,一个使用BeanFactory以及基于setter方法的依赖注射。下面是一个定义了一些bean的XmlBeanFactory 配置文件的一小部分。接下去是正式的bean的代码,演示了正确的setter方法声明。

<bean id="exampleBean" class="examples.ExampleBean">
    <property name="beanOne"><ref bean="anotherExampleBean"/></property>
    <property name="beanTwo"><ref bean="yetAnotherBean"/></property>
    <property name="integerProperty"><value>1</value></property>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {
    
    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }
    
    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }
    
    public void setIntegerProperty(int i) {
        this.i = i;
    }    
}

正如你所看到的一样,setter方法被声明以符合XML文件中指定的属性。(XML文件中的属性,直接对应着RootBeanDefinition中的PropertyValues对象)

接着是一个使用IoC type3(基于构造函数的依赖注射)的BeanFactory。下面是XML配置中的一段,指定了构造函数参数以及展示构造函数的代码:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
    <constructor-arg><ref bean="yetAnotherBean"/></constructor-arg>
    <constructor-arg><value>1</value></constructor-arg>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

正如你所看到的,bean定义中指定的构造函数参数将会作为ExampleBean的构造函数参数被传入。

现在考虑一下不用构造函数,而是调用一个静态工厂方法来返回一个对象的实例:

<bean id="exampleBean" class="examples.ExampleBean"
      factory-method="createInstance">
    <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg>
    <constructor-arg><ref bean="yetAnotherBean"/></constructor-arg>
    <constructor-arg><value>1</value></constructor-arg>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

public class ExampleBean {

    ...

    // a private constructor
    private ExampleBean(...) {
      ...
    }
    
    // a static factory method
    // the arguments to this method can be considered the dependencies of the bean that
    // is returned, regardless of how those arguments are actually used.
    public static ExampleBean ExampleBean(AnotherBean anotherBean,
                                          YetAnotherBean yetAnotherBean, int i) {
        ExampleBean eb = new ExampleBean(...);
        // some other operations
        ...
        return eb;
    }
}

需要注意的是:静态工厂方法的参数由 constructor-arg元素提供,这和构造函数的用法是一样的。这些参数是可选的。重要的一点是工厂方法所返回的对象类型不一定和包含这个静态工厂方法的类一致,虽然上面这个例子中是一样的。前面所提到的实例工厂方法(non-static)用法基本上是一样的(除了使用factory-bean属性代替class属性),在这里就不再详细叙述了。 .

3.3.2. 深入Bean属性和构造函数参数

正如前面提到的那样,bean的属性和构造函数参数可以被定义为其他bean的引用(合作者),或者内联定义的值。为了达到这个目的,XmlBeanFactory在property和constructor-arg元素中支持许多子元素类型。

value元素用适合人读的字符串形式指定属性或构造函数参数。正如前面提到的那样,JavaBeans的PropertyEditors被用来将这些字符串从java.lang.String类型转变为真实的属性类型或参数类型。

<beans>
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!-- results in a setDriverClassName(String) call -->
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://localhost:3306/mydb</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
    </bean>
</beans> 

null元素被用来处理null值。Spring将porperties等的空参数视为空的字符串。下面这个XmlBeanFactory配置:

<bean class="ExampleBean">
    <property name="email"><value></value></property>
</bean>        

导致email属性被设置为””,同java代码:exampleBean.setEmail("")等价。 而专门的<null>元素则可以用来指定一个null值,所以

<bean class="ExampleBean">
    <property name="email"><null/></property>
</bean>        

同代码:exampleBean.setEmail(null)是等价的.

list, set, map, 以及 props 元素可以用来定义和设置类型 为Java的List,Set, Map, 和 Properties .

<beans>
    ...
    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setPeople(java.util.Properties) call -->
        <property name="people">
            <props>
                <prop key="HarryPotter">The magic property</prop>
                <prop key="JerrySeinfeld">The funny property</prop>
            </props>
        </property>
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource"/>
            </list>
        </property>
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry key="yup an entry">
                    <value>just some string</value>
                </entry>
                <entry key="yup a ref">
                    <ref bean="myDataSource"/>
                </entry>
            </map>
        </property>
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource"/>
            </set>
        </property>

    </bean>
</beans>

注意:Map的entry或set的value,它们的值又可以是下面元素中的任何一个:

(bean | ref | idref | list | set | map | props | value | null)

property 元素中定义的bean元素用来定义一个内联的bean,而不是引用BeanFactory其他地方定义的bean。内联bean定义不需要任何id定义

<bean id="outer" class="...">
    <!-- Instead of using a reference to target, just use an inner bean -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name"><value>Tony</value></property>
            <property name="age"><value>51</value></property>
        </bean>
   </property>
</bean>

idref元素完全是一种简写和防止错误的方式,用来设置属性值为容器中其他bean的idname

<bean id="theTargetBean" class="...">
</bean>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

这个在运行时同下面的片段等价:

<bean id="theTargetBean" class="...">
</bean>
<bean id="theClientBean" class="...">
    <property name="targetName">
        <value>theTargetBean</value>
    </property>
</bean>

第一种形式比第二种形式更好的原因是:使用idref标记将会使Spring在部署的时候就验证其他的bean是否真正存在; 在第二种形式中,targetName属性的类仅仅在Spring实例化这个类的时候做它自己的验证,这很可能在容器真正部署完很久之后。

另外,如果被引用的bean在同一个xml文件中而且bean的名称是bean的 id,那么就可以使用local属性。它会让XML解析器更早,在XML文档解析的时候,验证bean的名称。

    <property name="targetName">
        <idref local="theTargetBean"/>
    </property>

ref元素是最后一个能在property元素中使用的元素。它是用来设置属性值引用容器管理的其他bean(可以叫做合作者)。正如前一节提到的,拥有这些属性的bean依赖被引用的bean,被引用的bean将会在属性设置前,必要的时候需要时初始化(如果是一个singleton bean可能已经被容器初始化)。所有的引用根本上是一个指向其他对象的引用,不过有3种形式指定被引用对象的id/name,这3种不同形式决定作用域和如何处理验证。

ref元素的bean属性指定目标bean是最常见的形式,它允许指向的bean可以在同一个BeanFactory/ApplicationContext(无论是否在同一个XML文件中)中,也可以在父BeanFactory/ApplicationContext中。bean属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中任何一个值相同。

    <ref bean="someBean"/>

local属性指定目标bean可以利用XML解析器的能力在同一个文件中验证XML id引用。local属性的值必须与目标bean的id属性一致。如果在同一个文件中没有匹配的元素,XML解析器将会产生一个错误。因此,如果目标bean在同一个XML文件中,那么使用local形式将是最好的选择(为了能够尽可能早的发现错误)。

    <ref local="someBean"/>

parent属性指定目标bean允许引用当前BeanFactory(ApplicationContext)的父BeanFactory(ApplicationContext)中的bean。parent属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中的一个值相同,而且目标bean必须在当前BeanFactory(ApplicationContext)的父BeanFactory(ApplicationContext)中。当需要用某种proxy包装一个父上下文中存在的bean(可能和父上下文中的有同样的name),所以需要原始的对象用来包装它。

    <ref parent="someBean"/>

3.3.3. 方法注入

对于大部分的用户来说,容器中多数的bean是singleton的。当一个singleton的bean需要同另一个singleton的 bean合作(使用)时,或者一个非singleton的bean需要同另一个非singleton的bean合作的时候,通过定义一个bean为另一个bean的属性来处理这种依赖的关系就足够了。然而当bean的生命周期不同的时候就有一个问题。想想一下一个singleton bean A, 或许在每次方法调用的时候都需要使用一个non-singleton bean B。容器仅仅会创建这个singleton bean A一次,因此仅仅有一次的机会去设置它的属性。因此容器没有机会每次去为bean A提供新的bean B的实例。

一个解决这个问题的方法是放弃一些反向控制。Bean A可以通过实现 BeanFactoryAware知道容器的存在(参见这里)),使用编程的手段(参见这里)在需要的时候通过调用getBean("B")来向容器请求新的bean B实例。 因为bean的代码知道Spring并且耦合于Spring,所以这通常不是一个好的方案。

方法注入,BeanFactory的高级特性之一,可以以清洁的方式处理这种情况以及其他一些情况。

3.3.3.1. Lookup方法注入

Lookup方法注射指容器能够重写容器中bean的抽象或具体方法,返回查找容器中其他bean的结果。 被查找的bean在上面描述的场景中通常是一个non-singleton bean (尽管也可以是一个singleton的)。Spring通过使用CGLIB库在客户端的类之上修改二进制码, 从而实现上述的场景要求。

包含方法注入的客户端类,必须按下面的形式的抽象(具体)定义方法:

protected abstract SingleShotHelper createSingleShotHelper();

如果方法不是抽象的,Spring就会直接重写已有的实现。在XmlBeanFactory的情况下,你可以使用bean定义中的lookup-method 属性来指示Spring去注入/重写这个方法,以便从容器返回一个特定的bean。举个例子说明:

<!-- a stateful bean deployed as a protype (non-singleton) -->
<bean id="singleShotHelper class="..." singleton="false">
</bean>

<!-- myBean uses singleShotHelper -->
<bean id="myBean" class="...">
  <lookup-method name="createSingleShotHelper"
                 bean="singleShotHelper"/>
  <property>
    ...
  </property>
</bean>

myBean需要一个新的singleShotHelper的实例的时候, 它就会调用它自己的createSingleShotHelper 方法。 值得注意的是:部署beans的人员必须小心地将singleShotHelper作为一个non-singleton部署 (如果确实需要这么做)。如果它作为一个singleton(除非明确说明,否则缺省就是singletion)而部署, 同一个singleShotHelper实例将会每次被返回。

注意Lookup方法注射能够同构造函数注射结合(对创建的bean提供可选的构造函数参数), 也可以同setter方法注射结合(在创建的bean之上设置属性)。

3.3.3.2. 任意方法的替换

另一种方法注射没有lookup方法注入用的多,它用另一个方法实现替换被管理bean的任意一个方法。用户可以放心跳过这一节(这是个有点高级的特性),除非这个功能确实需要。

在一个XmlBeanFactory中,对于一个被部署的bean, replaced-method元素可以用来把已存在的方法实现替换为其他的实现。 考虑如下的类,有一个我们想要重写的computeValue方法:

...
public class MyValueCalculator {
  public String computeValue(String input) {
    ... some real code
  }

  ... some other methods
}

需要为新方法定义提供实现 org.springframework.beans.factory.support.MethodReplacer接口的类。

/** meant to be used to override the existing computeValue
    implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ... 
        return ...;
}

部署原始的类和指定方法重写的BeanFactory部署定义象下面所示的 :

<bean id="myValueCalculator class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplaceMentComputeValue">
</bean>

replaced-method元素中的一个或多个arg-type 元素用来表示, 这个被重载方法的方法签名。 注意,参数的签名只有在方法被重载并且该方法有多个不同的形式的时候才真正需要。 为了方便,参数的类型字符串可以使全限定名的子字符串。比如,以下的都匹配 java.lang.String

    java.lang.String
    String
    Str

因为参数的个数通常就足够区别不同的可能,所以仅仅使用匹配参数的最短的字符串能够节省很多键入工作。

3.3.4. 使用 depends-on

对于大多数的情况,一个bean被另一个bean依赖,是由这个bean是否被当作其他bean的属性来表达的。 在XmlBeanFactory中,它是通过ref元素来完成的。 与这种方式不同的是,有时一个知道容器的bean仅仅会被给与它所的依赖的id (使用一个字符串值或等价的idref元素)。接着第一个bean就以编程的方式地向容器请求它的依赖。 在两种情况下,被依赖的bean都会在依赖它的bean之前被恰当地初始化。

对于相对罕见的情况,beans之间的依赖不够直接(举例,当一个类中的静态初始块需要被触发,比如数据库驱动的注册) ,depends-on 元素可以用来在初始化使用这个元素的bean之前,强制一个或多个beans初始化。

下面是一个配置的例子:

<bean id="beanOne" class="ExampleBean" depends-on="manager">
    <property name="manager"><ref local="manager"/></property>
</bean>

<bean id="manager" class="ManagerBean"/>

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 id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>

public class ExampleBean {
    public void init() {
        // do some initialization work
    }
}

同下面的完全一样:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

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 id="exampleInitBean" class="examples.ExampleBean" destroy-method="destroy"/>

public class ExampleBean {
    public void cleanup() {
        // do some destruction work (like closing connection)
    }
}

同下面的完全一样:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

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 id="inheritedTestBean" class="org.springframework.beans.TestBean">
    <property name="name"><value>parent</value></property>
    <property name="age"><value>1</value></property>
</bean>

<bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBean" init-method="initialize">
    <property name="name"><value>override</value></property>
    <!-- age should inherit value of 1 from parent -->
  </bean>

如果子bean定义没有指定class属性,将使用父定义的class属性,当然也可以覆盖它。 在后面一种情况中,子bean的class属性值必须同父bean的兼容,也就是它必须能够接受父亲的属性值。

一个子bean定义可以从父亲处继承构造函数参数,属性值以及方法,并且可以选择增加新的值。 如果init-method,destroy-method和/或静态factory-method被指定了,它们就会覆盖父亲相应的设置。

剩余的设置将 总是 从子定义处得到: 依赖, 自动装配模式, 依赖检查singleton, 延迟初始化

在下面的例子中父定义并没有指定class属性:

<bean id="inheritedTestBeanWithoutClass">
    <property name="name"><value>parent</value></property>
    <property name="age"><value>1</value></property>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
      parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name"><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 id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
    <property name="url"><value>${jdbc.url}</value></property>
    <property name="username"><value>${jdbc.username}</value></property>
    <property name="password"><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 id="messageSource" 
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <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 id="emailer" class="example.EmailBean">
    <property name="blackList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blackListListener" class="example.BlackListNotifier">
    <property name="notificationAddress">
        <value>[email protected]</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 id="placeholderConfig"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location"><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 id="sample" class="example.DependsOnExoticType">
    <property name="type"><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 id="customEditorConfigurer" 
    class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType">
                <bean class="example.ExoticTypeEditor">
                    <property name="format">
                        <value>upperCase</value>
                    </property>
                </bean>
            </entry>
        </map>
    </property>
</bean>

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

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

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

<bean id="myClass" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="staticMethod"><value>com.whatever.MyClassFactory.getInstance</value></property>
</bean>

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

<bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetClass"><value>java.lang.System</value></property>
  <property name="targetMethod"><value>getProperties</value></property>
</bean>
<bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject"><ref local="sysProps"/></property>
  <property name="targetMethod"><value>getProperty</value></property>
  <property name="arguments">
    <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替换。