spring beans beanfactory applicationcontext

第 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定义中有别名,则返回这些别名

 

leexhwhy 发表于:2005.05.08 14:16 ::分类: ( spring aop ) ::阅读:(443次) :: 评论 (0)
===========================================================
Beans, BeanFactory和ApplicationContext
===========================================================

第 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定义中有别名,则返回这些别名

 

leexhwhy 发表于:2005.05.08 14:16 ::分类: ( spring aop ) ::阅读:(346次) :: 评论 (0)
===========================================================
Spring 事物控制
===========================================================

 <bean id="txProxyTemplate" abstract="true"      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">     <property name="transactionManager">      <ref bean="transactionManager"/>     </property>     <property name="transactionAttributes">     <props>     <prop key="insert*">PROPAGATION_REQUIRED</prop>     <prop key="delete*">PROPAGATION_REQUIRED</prop>     <prop key="*">PROPAGATION_REQUIRED</prop>     </props>     </property>     </bean>

    <bean id="userManage" parent="txProxyTemplate">     <property name="target">     <ref local="orderMgr"/>     </property>     </bean>

调用的时候改成代理的名称就可以了.

leexhwhy 发表于:2005.04.12 17:56 ::分类: ( spring aop ) ::阅读:(582次) :: 评论 (1)
===========================================================
在Spring中实现mail操作
===========================================================

配置如下 

<!--配置mail服务器->

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">         <property name="host">             <value>mail.risesoft.net</value>         </property>     </bean>

<!--配置发送者的mail地址和标题-->

    <bean id="mailMessage" class="org.springframework.mail.SimpleMailMessage">         <property name="from">             <value>lixiaohai@risesoft.net</value>         </property>         <property name="subject">             <value>test spring mail</value>         </property>     </bean>

   <bean id="mailTest" class="test.mail.MailTest">         <property name="mailSender">             <ref local="mailSender"/>         </property>         <property name="message">             <ref local="mailMessage"/>         </property>     </bean>

 

MailTest.java

package test.mail;

import org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage;

/**  * Created by IntelliJ IDEA.  * User: Administrator  * Date: 2005-3-17  * Time: 15:40:44  * To change this template use File | Settings | File Templates.  */ public class MailTest implements IMailTest{     private MailSender mailSender;     private SimpleMailMessage message;

    public void setMailSender(MailSender mailSender) {         this.mailSender = mailSender;     }

    public void setMessage(SimpleMailMessage message) {         this.message = message;     }     public void test(){          SimpleMailMessage msg = new SimpleMailMessage(message);         msg.setTo("XXX@risesoft.net");         msg.setText("测试spring mail");         try{             mailSender.send(msg);         }catch(Exception ex){             ex.printStackTrace();         }     }

}

leexhwhy 发表于:2005.03.18 15:38 ::分类: ( spring aop ) ::阅读:(912次) :: 评论 (0)
===========================================================
利用Spring建立后台定时任务
===========================================================

利用Spring建立后台定时任务

原理:利用java.util.TimerTask的机制,由spring进行封装调用

步骤:

1:根据业务建立timerTask

public class MyTimeTask  extends TimerTask{

    private List mails;

 

    public void setMails(List mails) {

        this.mails = mails;

    }

 

    public void run() {

 

        System.out.println("开始执行计划任务。。。。。");

 

        for (Iterator iterator = mails.iterator(); iterator.hasNext();) {

 

            String s = (String) iterator.next();

 

            System.out.println("the mails :" + s);

 

        }

 

    }

 

 

}

2:配置timerTask

<bean id="myTask" class="test.MyTimeTask">

        <property name="mails">

            <list>

                <value>leexh@263.net</value>

                <value>lixiaohai@risesoft.net</value>

                <value>leexh1@263.net</value>

                <value>leexh2@263.net</value>

            </list>

        </property>

</bean>

3:配置第一层封装

<bean id="scheduleTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">

        <property name="delay">

 

            <value>10</value>

 

        </property>

 

        <property name="period">

 

            <value>10</value>

 

        </property>

 

        <property name="timerTask">

 

            <ref local="myTask"/>

 

        </property>

 

</bean>

 

4:配置第二次封装

<bean id="timer" class="org.springframework.scheduling.timer.TimerFactoryBean">

              <property name="scheduledTimerTasks">

 

                     <list>

 

                            <ref local="scheduleTask"/>

 

                     </list>

 

              </property>

 

       </bean>

 

这样在load完成application-context.xml后,后台定时任务就会自动运行

 

以后只要添加新的timertask,配置第一次封装,将第一次封装的scheduleTask加入到timerscheduledTimerTasks属性列表就可以了。

 

 

 

 

 

 

 

 

leexhwhy 发表于:2005.03.02 18:43 ::分类: ( spring aop ) ::阅读:(4270次) :: 评论 (61)
===========================================================
在spring 框架中将配置文件分层次配置方法的使用
===========================================================

spring 框架中将配置文件分层次配置方法的使用

1:如果应用服务器不能访问外网,则在application-context.xml配置的dtd将失效,此时将

       <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">修改为:

       <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ":/spring-beans.dtd">

(注意:":"不可以省略)

2:实际应用中应该将spring bean的配置文件根据业务模块分文件放置,将公用bean的配置放在比如commons-spring-context.xml中,

将每个模块私有的bean信息放在各自的配置文件中,这样不需要维护一个超大的配置文件。

3:操作方法:    

       1:在子配置文件增加以下:

       <?xml version="1.0"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!—在这里引用通用的xml配置-->

    <import resource="commons-spring-context.xml"/>

 

    <bean id="transactionManager"

        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource">

                     <!—将原来local改为parent以引用通用配置文件的bean-->

            <ref parent="dataSource"/>

        </property>

    </bean>

       .....

leexhwhy 发表于:2005.03.02 18:42 ::分类: ( spring aop ) ::阅读:(513次) :: 评论 (0)
===========================================================
Spring 框架中基本使用和数据持久层使用方法测试
===========================================================

Spring 框架中基本使用和数据持久层使用方法测试

Ø         测试目的测试世纪应用中spring框架的使用以及spring框架的数据库持久层的使用方法.

Ø         测试用例以及数据库脚本

n         模拟简单的订单管理

n         简单的订单和订单条目表(父子表)

n         Create table myorder(

id number(10) primary key,

name varchar2(200)

);

n         Create table myorder_item(

id number(10) primary key,

order_id number(10),

name varchar2(200)

);

Alter table myorder_item add constraint myorder_item_fk foreign key(order_id) references myorder(id);

Ø         建立modelbean和数据库表对应

public class Order {

    private int id;

    private String name;

    private List items;

    public int getId() {

        return id;

    }

    public void setId(int id) {

        this.id = id;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public String toString(){

        StringBuffer sb = new StringBuffer();

        sb.append("[id]" ).append(id).append(";[name]").append(name);

        return sb.toString();

    }

}

Ø         创建DAO接口

public interface OrderDAO {

    public Order getOrder(int id) throws DataAccessException;

    public List getOrdersByXXX(String cause) throws DataAccessException;

    public void insertOrder(Order order) throws DataAccessException;

    public void deleteOrder(int id)throws DataAccessException;

    public void updateOrder(Order order)throws DataAccessException;

}

Ø         创建DAO实现类

Ø         注意:spring框架的数据库持久层要求DAO实现类包含datasource的属性,业务层调用的时候利用aop的类注射,datasource注入到DAO,具体原理参见aop手册.

Ø        

public class OrderDAOImpl implements OrderDAO {

    private DataSource ds;

    public void setDs(DataSource ds) {

        this.ds = ds;

    }

public Order getOrder(final int id) throws DataAccessException {

//spring框架数据库持久层使用方法:

//1:创建JdbcTemplate实例

//2:拼写sql语句

//调用JdbcTemplatequery方法或者update方法

//建立内部匿名类。重载接口的实现方法,基本上包括以下接口

//a:如果sql语句中使用preparedStament,则需要实现springPreparedStatementSetter接口,重载setValues方法,设置PreparedStatement的值

//b如果结果集只包含一条记录(比如按照主键获得数据),则在query方法中需要重载RowMappermapRow方法,如果获得多条数据,则重载RowCallbackHandlerprocessRow方法,处理结果集和java对象的映射(原理就是手工进行sql和对象之间的or mapping

//spring框架的数据库持久层机制和其他or mapping的比较:

//hibernate或者其他or mapping工具解决了数据库操作的大部分工作,但是学习难度比较大,处理本地sql的能力不足,遇到特殊需求,比如需要使用oracle的统计分析函数的时候还是需要手工编写sql处理,spring的处理机制,以增加代码量为代价获得一个灵活的机制。但是spring的方式要编写很多内部匿名类,代码有点凌乱,Callback类的编写似乎也有悖于日常的编程习惯。但是这并不影响spring框架的使用,spring框架对hibernate支持的很好,只要对DAO层使用针对接口编程,改成hibernate是很容易的,而且并不影响业务层。

        JdbcTemplate jt = new JdbcTemplate(ds);

        final Order order = new Order();

        String sql = "select * from myorder where id=?";

        jt.query(sql, new PreparedStatementSetter() {

            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setInt(1, id);

            }

        }, new RowMapper() {

            public Object mapRow(ResultSet rs, int rowNum) throws SQLException {

                order.setId(rs.getInt(1));

                order.setName(rs.getString(2));

                return order;

            }

        });

        return order;

    }

    public List getOrdersByXXX(String cause) throws DataAccessException {

        JdbcTemplate jt = new JdbcTemplate(ds);

        final List orders = new ArrayList();

        String sql = "select * from myorder";

        jt.query(sql, new RowCallbackHandler() {

            public void processRow(ResultSet rs) throws SQLException {

                Order order = new Order();

                order.setId(rs.getInt(1));

                order.setName(rs.getString(2));

                orders.add(order);

            }

        });

        return orders;

    }

    public void insertOrder(final Order order) throws DataAccessException {

        JdbcTemplate jt = new JdbcTemplate(ds);

        String sql = "insert into myorder values(?,?)";

        jt.update(sql, new PreparedStatementSetter() {

            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setInt(1, order.getId());

                ps.setString(2, order.getName());

            }

        });

//       jt.

    }

    public void deleteOrder(final int id) throws DataAccessException {

        JdbcTemplate jt = new JdbcTemplate(ds);

        String sql = "delete from myorder where id=?";

        jt.update(sql, new PreparedStatementSetter() {

            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setInt(1, id);

            }

        });

    }

    public void updateOrder(final Order order) throws DataAccessException {

        JdbcTemplate jt = new JdbcTemplate(ds);

        String sql = "update   myorder set name=? where id=?";

        jt.update(sql, new PreparedStatementSetter() {

            public void setValues(PreparedStatement ps) throws SQLException {

                ps.setString(1, order.getName());

                ps.setInt(2, order.getId());

            }

        });

    }

}

Ø         创建业务层接口

public interface IOrderMgr {

    public List getOrdersbyXXX(String cause) throws DataAccessException;

    public void insertOrder(Order order) throws DataAccessException;

    public void updateOrder(Order order) throws DataAccessException;

    public void deleteOrder(int id) throws DataAccessException;

    public Order getOrder(int id) throws DataAccessException;

    public List getOrderItemsbyXXX(String cause) throws DataAccessException;

    public List getOrderItemsbyOrder(int orderId) throws DataAccessException;

    public void insertOrderItem(OrderItem order) throws DataAccessException;

    public void updateOrderItem(OrderItem order) throws DataAccessException;

    public void deleteOrderItem(int id) throws DataAccessException;

    public void deleteOrderItemByOrder(int OrderId) throws DataAccessException;

    public OrderItem getOrderItem(int id) throws DataAccessException;

}

Ø         创建业务层接口实现

Ø         业务层调用DAO层的接口,同样也是利用IOC在调用的时候将类注射到业务层

public class OrderMgr implements IOrderMgr {

    private OrderDAO orderDAO;

    private OrderItemDAO orderItemDAO;

    public void setOrderDAO(OrderDAO orderDAO) {

        this.orderDAO = orderDAO;

    }

    public void setOrderItemDAO(OrderItemDAO orderItemDAO) {

        this.orderItemDAO = orderItemDAO;

    }

    public List getOrdersbyXXX(String cause) throws DataAccessException {

        return orderDAO.getOrdersByXXX(cause);

    }

    public void insertOrder(Order order) throws DataAccessException {

        orderDAO.insertOrder(order);

    }

    public void updateOrder(Order order) throws DataAccessException {

        orderDAO.updateOrder(order);

    }

    public void deleteOrder(int id) throws DataAccessException {

        orderItemDAO.deleteOrderItemByOrder(id);

        orderDAO.deleteOrder(id);

    }

    public Order getOrder(int id) throws DataAccessException {

<p class="MsoNormal

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

leexhwhy 发表于:2005.03.02 18:39 ::分类: ( spring aop ) ::阅读:(777次) :: 评论 (4)
===========================================================
常用oracle脚本,补充中......
===========================================================
查询关联表 1:find_child.sql    SELECT table_name "Child tables" FROM user_constraints WHERE r_constraint_name IN (SELECT constraint_name FROM user_constraints WHERE table_name = UPPER('&parent_tab_nm') AND constraint_type = 'P'); 2:find_parent.sql rem Find all parent tables for a given table. Run this script as the owner of the rem  tables. If not the owner, use ALL_CONSTRAINTS or DBA_CONSTRAINTS rem  views, instead of USER_CONSTRAINTS. SELECT prnt.table_name "Parent Tables" FROM user_constraints prnt, user_constraints chld WHERE chld.table_name = UPPER('&child_tbl_nm') AND prnt.constraint_type = 'P' AND chld.r_constraint_name = prnt.constraint_name; 3:查找死锁 set linesize 132 pagesize 66  break on Kill on username on terminal  column Kill heading 'Kill String' format a13  column res heading 'Resource Type' format 999  column id1 format 9999990  column id2 format 9999990  column lmode heading 'Lock Held' format a20  column request heading 'Lock Requested' format a20  column serial# format 99999  column username  format a10  heading "Username"  column terminal heading Term format a6  column tab format a35 heading "Table Name"  column owner format a9  column Address format a18  select nvl(S.USERNAME,'Internal') username,  nvl(S.TERMINAL,'None') terminal,  L.SID||','||S.SERIAL# Kill,  U1.NAME||'.'||substr(T1.NAME,1,20) tab,  decode(L.LMODE,1,'No Lock',  2,'Row Share',  3,'Row Exclusive',  4,'Share',  5,'Share Row Exclusive',  6,'Exclusive',null) lmode,  decode(L.REQUEST,1,'No Lock',  2,'Row Share',  3,'Row Exclusive',  4,'Share',  5,'Share Row Exclusive',  6,'Exclusive',null) request  from V$LOCK L,   V$SESSION S,  SYS.USER$ U1,  SYS.OBJ$ T1  where L.SID = S.SID   and T1.OBJ# = decode(L.ID2,0,L.ID1,L.ID2)   and U1.USER# = T1.OWNER#  and S.TYPE != 'BACKGROUND'  order by 1,2,5  / 
leexhwhy 发表于:2005.02.01 13:17 ::分类: ( oracle ) ::阅读:(455次) :: 评论 (0)
===========================================================
Spring 编程入门十大问题解答
===========================================================
1、如何学习Spring?   你可以通过下列途径学习spring:   (1) spring下载包中doc目录下的MVC-step-by-step和sample目录下的例子都是比较好的spring开发的例子。   (2) AppFuse集成了目前最流行的几个开源轻量级框架或者工具 Ant,XDoclet,Spring,Hibernate(iBATIS),JUnit,Cactus,StrutsTestCase,Canoo's WebTest,Struts Menu,Display Tag Library,OSCache,JSTL,Struts 。   你可以通过AppFuse源代码来学习spring。 AppFuse网站:http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse   (3)Spring 开发指南(夏昕)(http://www.xiaxin.net/Spring_Dev_Guide.rar)   一本spring的入门书籍,里面介绍了反转控制和依赖注射的概念,以及spring的bean管理,spring的MVC,spring和hibernte,iBatis的结合。   (4) spring学习的中文论坛   SpringFramework中文论坛(http://spring.jactiongroup.net)   Java视线论坛(http://forum.javaeye.com)的spring栏目   2、利用Spring框架编程,console打印出log4j:WARN Please initialize the log4j system properly?   说明你的log4j.properties没有配置。请把log4j.properties放到工程的classpath中,eclipse的classpath为bin目录,由于编译后src目录下的文件会拷贝到bin目录下,所以你可以把log4j.properties放到src目录下。   这里给出一个log4j.properties的例子:
log4j.rootLogger=DEBUG,stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %5p (%F:%L) - %m%n
  3、出现 java.lang.NoClassDefFoundError?   一般情况下是由于你没有把必要的jar包放到lib中。   比如你要采用spring和hibernate(带事务支持的话),你除了spring.jar外还需要hibernat.jar、aopalliance.jar、cglig.jar、jakarta-commons下的几个jar包。 http://www.springframework.org/download.html下载spring开发包,提供两种zip包 spring-framework-1.1.3-with-dependencies.zip和spring-framework-1.1.3.zip,我建议你下载spring-framework-1.1.3-with-dependencies.zip。这个zip解压缩后比后者多一个lib目录,其中有hibernate、j2ee、dom4j、aopalliance、jakarta-commons等常用包。   4、java.io.FileNotFoundException: Could not open class path resource [....hbm.xml],提示找不到xml文件?   原因一般有两个:   (1)该xml文件没有在classpath中。   (2)applicationContext-hibernate.xml中的xml名字没有带包名。比如:
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean"> <property name="dataSource"><ref bean="dataSource"/></property> <property name="mappingResources">  <list>   <value>User.hbm.xml</value>   错,改为:   <value>com/yz/spring/domain/User.hbm.xml</value>  </list> </property> <property name="hibernateProperties"> <props>  <prop key="hibernate.dialect"> net.sf.hibernate.dialect.MySQLDialect </prop>  <prop key="hibernate.show_sql">true</prop> </props> </property> </bean>
  5、org.springframework.beans.NotWritablePropertyException: Invalid property 'postDao' of bean class?   出现异常的原因是在application-xxx.xml中property name的错误。   <property name="...."> 中name的名字是与bean的set方法相关的,而且要注意大小写。   比如
public class PostManageImpl extends BaseManage implements PostManage {  private PostDAO dao = null;  public void setPostDAO(PostDAO postDAO){   this.dao = postDAO;  } }
  那么xml的定义应该是:
<bean id="postManage" parent="txProxyTemplate"> <property name="target">  <bean class="com.yz.spring.service.implement.PostManageImpl">   <property name="postDAO"><ref bean="postDAO"/></property> 对   <property name="dao"><ref bean="postDAO"/></property> 错  </bean> </property> </bean>
  6、Spring中如何实现事务管理?   首先,如果使用mysql,确定mysql为InnoDB类型。   事务管理的控制应该放到商业逻辑层。你可以写个处理商业逻辑的JavaBean,在该JavaBean中调用DAO,然后把该Bean的方法纳入spring的事务管理。   比如:xml文件定义如下:
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"><ref bean="transactionManager"/></property> <property name="transactionAttributes"> <props> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="remove*">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="userManage" parent="txProxyTemplate">  <property name="target">   <bean class="com.yz.spring.service.implement.UserManageImpl">    <property name="userDAO"><ref bean="userDAO"/></property>   </bean>  </property> </bean>
  com.yz.spring.service.implement.UserManageImpl就是我们的实现商业逻辑的JavaBean。我们通过parent元素声明其事务支持。   7、如何管理Spring框架下更多的JavaBean?   JavaBean越多,spring配置文件就越大,这样不易维护。为了使配置清晰,我们可以将JavaBean分类管理,放在不同的配置文件中。 应用启动时将所有的xml同时加载。   比如:   DAO层的JavaBean放到applicationContext-hibernate.xml中,商业逻辑层的JavaBean放到applicationContext-service.xml中。然后启动类中调用以下代码载入所有的ApplicationContext。
String[] paths = {"com/yz/spring/dao/hibernate/applicationContext-hibernate.xml", "com/yz/spring/service/applicationContext-service.xml"}; ctx = new ClassPathXmlApplicationContext(paths);
  8、web应用中如何加载ApplicationContext?   可以通过定义web.xml,由web容器自动加载。
<servlet> <servlet-name>context</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-hibernate.xml</param-value> <param-value>/WEB-INF/applicationContext-service.xml</param-value> </context-param>
  9、在spring中如何配置的log4j?   在web.xml中加入以下代码即可。
<context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param>
  10、Spring框架入门的编程问题解决了,我该如何更深地领会Spring框架呢?   这两本书你该去看看。这两本书是由Spring的作者Rod Johnson编写的。
Expert One on one J2EE Design and Development Expert One on one J2EE Development Without EJB
  你也该看看martinfowler的Inversion of Control Containers and the Dependency Injection pattern。
http://www.martinfowler.com/articles/injection.html
    再好好研读一下spring的文档。
http://www.jactiongroup.net/reference/html/index.html(中文版,未全部翻译)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值