http://www.matrix.org.cn/article/1052.html
在Spring中,两个最基本最重要的包是:org.springframework.beans和org.springframework.context包。这两个包中的代码为Spring的反向控制特性(也叫作依赖注射)提供了基础。BeanFactory[http://www.springframework.org/docs/api/org/ ;springframework/beans/factory/BeanFactory.html]提供了能够管理任何种类beans(对象)的先进的配置机制,潜在地利用任何一种存储设备。ApplicationContext建立在BeanFactory之上并增加了其他的功能,比如同Spring AOP特性更容易整合,信息资源处理(用于国际化),事件传播,声明式机制用于创建ApplicationContext和可选的父上下文以及与应用层相关的上下文(比如WebApplicationContext),以及其他的增强。
简而言之,BeanFactory提供了配置框架和基本的功能,而ApplicationContext为它增加了更强的功能,这些功能中的一些或许更加J2EE和企业中心(enterprise-centric)。一般来说,ApplicationContext是BeanFactory的完全超集,任何BeanFactory功能和行为的描述也同样被认为适用于ApplicationContext。
用户有时在特定的场合下不确定BeanFactory和ApplicationContext哪一个更适于使用。通常大部分在J2EE环境中构建的应用最好的选择是使用ApplicationContext,因为它不仅提供了BeanFactory所有的特性以及它自己附加的特性,而且还提供更声明化的方法去使用一些功能,这通常是令人满意的。你最好选择BeanFactory的主要场景通常是当内存使用是最主要的关注(比如在一个每kb都要计算的applet中),而且你也不需要ApplicationContext所有特性的时候。
这一章粗略地分为两部分,第一部分包括同时适用于BeanFactory和ApplicationContext的基本原则。第二部分会包括仅仅适用于ApplicationContext的一些特性。
3.2.BeanFactory和Bean定义(基础)
3.2.1.BeanFactory
BeanFactory是实际上实例化,配置和管理许多beans的容器。这些beans通常互相之间合作,因而也在它们之间产生依赖。这些依赖反映在被BeanFactory使用的配置数据中(一些依赖可能不像配置数据一样可见,而更可能在运行期作为bean之间程序交互的函数)。
一个BeanFactory用接口org.springframework.beans.factory.BeanFactory表示,这个接口有多个实现。最常使用的的简单的BeanFactory实现是org.springframework.beans.factory.xml.XmlBeanFactory。(这带有如下的暗示:ApplicationContext是BeanFactory的子类,所以大多数的用户更喜欢使用ApplicationContext的XML形式)。
尽管对于大多数场景,几乎所有的被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层提供支持代码,用来自动读取一个Spring ApplicationContext并把读取过程作为一个J2EE web应用启动过程的一部分。这个声明式的过程在这里描述:
编程式地操纵BeanFactory将会在后面接受,下面部分将集中描述BeanFactory的配置。
在最基础的级别上,一个BeanFactory配置由一个或多个BeanFactory必须管理的Bean的定义组成。在一个XmlBeanFactory中,在顶级的<beans>元素中配置一个或多个<bean>元素。
3.2.2.Bean定义
一个XmlBeanFactory实体中的Bean定义包括如下的细节以及其他一些信息:
l 一个classname:通常这是Bean定义中描述的那个bean的真正的实现类。然而如果一个bean使用一个静态工厂方法所创建而不是被普通的构造函数创建,那么这里实际上就是工厂类的classname。
l Bean行为配置元素:声明这个bean在容器的行为方式(比如prototype或singleton,自动装配模式,依赖检查模式,初始化和析构方法)。
l 构造函数参数和需要赋予新创建bean的属性:举一个例子,一个管理连接池的bean使用的连接数目(即可以指定为一个property也可以作为一个构造函数参数),或者池大小的限制数目。
l 这个bean需要起作用的其他beans:比如它的合作者(同业也可以作为属性或者构造函数的参数)。这个也被叫做依赖。
上面列出的概念直接转化为组成bean定义的一组元素。这些元素在下面的表格中列出,关于它们每一个的更详细的说明都有一个链接相对应。
表3.1。Bean定义说明
特性 更多信息
class 3.2.3节,bean class
id和name 3.2.4节,bean标志符(id和name)
singleton或prototype 3.2.5节,使用singleton还是不使用呢
构造函数参数 3.3.1节,设置bean的属性和合作者
bean属性 3.3.1节,设置bean的属性和合作者
自动装配模式 3.3.5节,自动装配合作者
依赖检查模式 3.3.6节,检查依赖
初始化方法 3.4.1节,生命周期接口
析构方法 3.4.1节,生命周期接口
注意bean定义是由真实的接口org.springframework.beans.factory.config.BeanDefinition以及它的各种子接口和实现所表示的。然而,绝大多数的用户代码不需要与BeanDefination直接作用。
3.2.3.bean class
class属性通常是强制性的(参看3.2.3.3节-通过实例工厂方法创建bean和3.5节-child bean的定义这两个例外情况),被用来达到两个目的中的一个。在绝大多数普通的场景中BeanFactory自己直接调用bean的构造函数来创建bean(相当于调用new的Java代码),class属性指定了需要创建的bean的类。在比较少的普通场景中BeanFactory调用某个类的静态的所谓的工厂方法来创建bean,class属性指定了实际包含静态工厂方法的那个类。(至于静态工厂方法返回的bean的类型是同一个类还是完全不同的另一个类,这并不重要)。
3.2.3.1.通过构造方法创建bean
当使用构造函数方式创建bean的时候,所有普通的类都可以被Spring使用以及同Spring兼容。这就是说,被创建的类不需要实现任何指定的接口或者按照特定的样式进行编码。仅仅指定bean的类就足够了。然而,由于依赖你给特定bean使用的IoC类型,你可能需要一个默认的(空的)构造函数。
另外,BeanFactory并不局限于管理真正的JavaBeans,事实上它也能够管理任何你想让它帮你管理的类。虽然很多使用Spring的人喜欢在BeanFactory中用真正的JavaBeans(仅包含一个默认的(无参数的)构造方法以及在属性后面包含适当的相对应的setter和getter),但是在你的BeanFactory中也可以使用特殊的非bean样式的类。举例来说,如果你需要使用一个遗留下来的完全没有遵守JavaBean规范的连接池,不要担心,Spring同样能够管理它。
使用XmlBeanFactory你可以像如下这样制定你的bean class:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/> |
至于为构造方法提供参数(可选的),以及对象实例构造后设置实例的属性,将会在后面叙述。
3.2.2.2.通过静态工厂方法创建Bean
当你定义一个使用静态工厂方法创建的bean,同时使用class属性指定包含静态工厂方法的类,这个时候就需要另一个叫做factory-method的属性来指定工厂方法的名字。Spring期待能够调用这个方法(包含可选的一组参数将在后面叙述)并且返回一个有效的对象,之后这个对象就会如同用构造方法创建的对象一样被看待。用户可以使用这样的bean定义在遗留代码中调用静态工厂。
下面是一个bean定义的例子,声明了一个bean要通过调用一个工厂方法被创建。要注意定义并没有指定返回对象的类型,只指定包含工厂方法的类。在这个例子中,createInstance必须是静态方法。
<bean id="exampleBean" class="examples.ExampleBean2" factory-method="createInstance"/> |
至于为工厂方法提供参数(可选的),以及对象实例被工厂方法返回后设置该实例的属性,将会在后面叙述。
3.2.3.3.通过实例工厂方法创建bean
与使用静态工厂方法创建bean非常类似的是,使用一个实例工厂方法(非静态的),通过调用一个已存在bean(这个bean应该是工厂类型)的工厂方法来创建新的bean。
要使用这种机制,class属性必须保留为空的,而且factory-bean属性必须指定一个包含工厂方法的bean的name(¥¥)。而工厂方法本身仍然要通过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属性的机制将在后面讨论,这个方案有一个暗示就是factory bean本身能够被容器通过依赖注射来管理和配置。
3.2.4.bean的标志符(id和name)
每一个bean都有一个或多个ids(也叫作标志符,或names;这些名词说的是一回事)。这些id在bean被管理的BeanFactory或ApplicationContext中必须是唯一的。一个bean差不多总是只有一个id,但是如果一个bean有超过一个的id,那么另外的那些本质上可以认为是别名。在一个XmlBeanFactory中(包括ApplicationContext的各种形式),你可以用id或者name属性来指定bean的id(s),并且在这两个或其中一个属性中至少指定一个id。id属性允许你指定一个id,并且它在XML DTD(定义文档)中作为一个真正的XML元素的ID属性被标记,所以parser能够在其他元素指回向它的时候做一些额外的校验。正因如此,用id属性指定bean的id是一个更好的方式。然而,XML规范严格限定了在XML ID中合法的字符。通常这并不是一个实在的约束,但是如果你有必要使用这些字符(在ID中的非法字符)中的一个,或者你想给bean引进其他的别名,那么你可以通过name属性指定一个或多个ids(用逗号“,”或者分号“;”分开)。
3.2.5.使用singleton还是不使用呢
Beans被定义为两种部署模式中的一种:singleton或non-singleton。(后一种也别叫作prototype,尽管这个名词用的不精确因为它并不是非常适合)。如果一个bean是singleton的,那么仅有一个共享的这个bean的实例被管理,所有id或者ids与这个bean定义匹配的beans请求都会导致那个唯一特定的bean实例被返回。
而non-singleton,prototype模式的bean部署产生的后果是:对这个bean的每次请求都会创建一个新的bean实例。这对于例如每个user需要一个独立的user对象这样的情况是非常理想的。
Beans默认被部署为singleton模式,除非你指定为另外的。要紧记把类型变为non-singletion(prototype)后,每一次对这个bean的请求都会导致一个新创建的bean而且这可能并不是你真正想要的。所以仅仅在绝对需要的时候才把模式改成prototype的。
在下面这个例子中,两个bean一个被定义为singleton的而另一个被定义为non-singleton(prototype)的。exampleBean会在客户端每次向BeanFactory请求它的时候被创建,而yetAnotherExample仅仅被创建一次;在对它每次请求的时候这个实例的引用会被返回。
<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之后,真正地注射这些依赖。This is fundamentally the inverse (hence the name Inversion of Control) of the bean instantiating or locating its dependencies on its own using direct construction of classes,就像ServiceLocator模式一样。我们不会详细阐述依赖注射的优点,很显然通过使用它:代码变得非常清晰;当beans不再自己查找他们的依赖而是由容器提供,甚至不需要知道在何处定位依赖以及依赖实际上是什么类型,这时高层次的解耦也变得很容易了。
正如上面提到的那样,反向控制/依赖注射存在两种主要的形式:
l 基于setter的依赖注射,是在调用无参的构造函数或无参的静态工厂方法实例化你的bean之后,通过在你的bean上调用setter实现的。在BeanFactory中定义的使用基于setter的注射依赖的bean是真正的JavaBeans。Spring一般提倡使用基于setter的依赖注射,因为很多的构造函数参数将会是笨重的,尤其在有些属性是可选的情况下。
l 基于构造函数的注射依赖,它是通过调用带有许多参数的构造方法实现的,每个参数表示一个合作者或者属性。另外,调用带有特定参数静态工厂方法来构造bean可以被认为差不多等同的,接下来的文字会把构造函数的参数和静态工厂方法的参数看作一样的。虽然Spring一般提倡在大多数情况下使用基于setter的依赖注射,但是Spring还是完全支持基于构造函数的依赖注射,因为你可能想要把它用在那些只提供多参数构造函数并且没有setter的遗留的beans上。另外对于一些比较简单的beans,一些人更喜欢使用构造函数方法以确保beans不会被以错误的形式构造。
BeanFactory同时支持这两种将依赖注射到被管理bean中的方式。(实际上它还支持在一些依赖已经通过构造函数方法注射后再使用基于setter的依赖注射)。依赖的配置是以BeanDefinition的形式出现,它和JavaBeans的PropertyEditors一起使用来了解如何把properties从一个格式转变为另一个。真正传送的值被变为Propertyvalue对象的形式。然而,大多数Spring的使用者并不要直接(比如编程式地)处理这些classes,而更多地使用一个XML定义文件,这个文件会在内部被转变为这些classes的实例,用来读取整个BeanFactory或ApplicationContext。
Bean依赖的决定通常按照下面所示发生:
l BeanFactory通过使用一个描述所有beans的配置被创建和实例化。大多数的Spring用户使用一个支持XML格式配置文件的BeanFactory或ApplicationContext变体。
l 每一个bean的依赖都是属性的形式,构造函数参数的形式,或者当用静态工厂方法代替普通构造函数时工厂方法的参数。这些依赖将会在bean真正被创建出来后提供给bean。
l 每一个属性或者构造函数参数要么是一个要被设置的值的真实定义,要么是一个指向BeanFactory中其他bean的引用。在ApplicationContext的情况下,这个引用可以指向一个父亲ApplicationContext中bean。
l 每一个是真实值的属性或构造函数参数,必须能够从(配置文件中)被指定的格式转变为属性或构造函数参数的真实类型。默认的Spring能够把一个字符串格式的值转变为所有内建的类型,比如int, long, String, boolean等等。另外当说到基于XML的BeanFactory变体的时候(包括ApplicationContext变体),它们已经为定义Lists, Maps, Sets和Properties集合类型提供了内在的支持。另外,Spring通过使用JavaBeans的PropertyEditor定义从而能够将字符串值转变为其他任意的类型。(你可以为BeanFactory提供你自己的PropertyEditor定义从而能够转变你自定义的类型;更多关于PropertyEditors的信息以及如何手动增加自定义的PropertyEditors请参看3.9节,注册附加的自定义PropertyEditors)。当一个bean属性是一个Java Class类型,Spring允许你用这个class的名字的字符串作为这个属性的值,ClassEditor这个内建的PropertyEditor会帮你把class的名字转变成真实的Class实例。
l 很重要的一点就是要了解:Srping在BeanFactory创建的时候要校验BeanFactory中每一个Bean的配置。这些校验包括作为Bean引用的属性必须实际引用一个合法的beans(比如被引用的bean也定义在BeanFactory中,或者当ApplicationContext时,在父亲上下文中)。但是,bean属性本身直到bean被真实建立的时候才被设置。对于那些是singleton并且被设置为pre-instantiated的bean来说(比如一个ApplicationContext中的singleton bean),Bean创建发生在BeanFactory被创建的时候,但是对于其他情况,发生在bean被请求的时候。当一个bean必须被创建时,它会潜在地导致一系列的其他bean被创建,像它的依赖以及它的依赖的依赖(如此下去)被创建和赋值。
l 通常你可以信任Spring做了正确的事情。它会在BeanFactory装载的时候检查处错误,包括对不存在bean的引用和循环引用。它会尽可能晚地真正设置属性和解决依赖(比如创建那些需要的依赖),在bean真正被创建的时候。这就意味着:就算一个BeanFactory被正确地装载,稍后当你请求一个bean的时候,如果创建那个bean或者它的依赖的时候出现了错误,这个BeanFactory也会生成一个异常。比如,如果一个bean抛出一个异常作为缺少或非法属性的结果,这样的情况就会发生。这种潜在地推迟一些配置错误可见性的行为正是ApplicationContext默认预实例化singleton beans的原因。以前期的时间和内存为代价在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的构造函数参数被传入。现在考虑一下替换掉构造函数的变体,Spring被告知调用一个静态工厂方法来返回一个对象的实例:
<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元素被支持,构造函数事实上也是一样的被使用。这些参数是可选的。很重要的一点是工厂方法所返回的class类型不一定要和包含着个静态工厂方法的class一致,虽然上面这个例子中是一样的。前面所提到的实例工厂方法(non-static)用法基本上是一样的(除了使用factory-bean属性代替class属性),在这里就不再详细叙述了。
3.3.2.深入Bean属性和构造函数参数
正如前面提到的那样,bean的属性和构造函数参数可以被定义为其他managed beans的引用(合作者),或者内联定义的值。为了达到这个目的,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的id 或name的字符串来设置属性。
<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属性的class仅仅会在这个class被Spring实例化的时候做它自己的验证,很可能在容器真正部署完很久之后。
另外,如果被引用的bean在同一个xml文件中而且bean的名称是bean的id,那么local属性就可以被使用。它会让XML parser自己更早地验证bean的名称,在XML文档解析的时候。
<property name="targetName"> <idref local="theTargetBean"/> </property> |
ref元素是最后一个能在property元素中使用的元素。它是用来给一些特定的属性设值的,这些属性是容器管理的其他bean的引用(可以叫做合作者)。正如前一节提到的,被引用的bean被认为是那些属性要被设置的bean的依赖,被应用的bean将会在属性设置之前需要时(如果是一个singleton bean可能已经被容器初始化),一经请求就被初始化。所有的引用根本上就只是一个指向其他对象的引用,不过这里其它对象的id/name如何被指定有3种形式,这3种不同形式决定scoping和验证如何被处理。
用ref标签的bean属性指定目标bean是最常见的形式,对于同一个BeanFactory/ ApplicationContext(无论是否在同一个XML文件中)中,或者parent BeanFactory/ ApplicationContext中的任何bean,允许创建指向它的bean。bean属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中任何一个值相同。
<ref bean="someBean"/>
用local属性指定目标bean可以利用XML parser的能力在同一个文件中验证XML id引用。local属性的值必须与目标bean的id属性一致。如果在同一个文件中没有匹配的元素,XML parser将会产生一个错误。因此,如果目标bean在同一个XML文件中,那么使用local形式将是最好的选择(为了能够尽可能早的发现错误)。
<ref local="someBean"/>
用parent属性指定目标bean允许一个引用指向当前BeanFactory(ApplicationContext)的parent BeanFactory(ApplicationContext)中的bean。Parent属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中的一个值相同,而且目标bean必须在当前BeanFactory(ApplicationContext)的parent中。当需要用某些proxy包装一个parent上下文中存在的bean(可能和parent中的有同样的name),所以需要原始的对象用来包装它。
<ref parent="someBean"/>
3.3.3.方法注射
对于大部分的用户来说,容器中多数的beans将会是singletons。当一个singleton bean需要同另一个singleton bean合作(使用),或者一个non-singleton bean需要同另一个non-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库在客户端的class之上修改二进制码,从而实现上述的场景要求。
在客户端的class包含需要注入的方法,方法定义必须是如下形式的抽象(具体)定义:
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(不管是明确指出的还是默认的)而部署,同一个singleShotHelper实例将会每次被返回。
Lookup方法注射能够同构造函数注射结合(对创建的bean提供可选的构造函数参数),也可以同setter注射结合(在创建的bean之上设置属性)。
3.3.3.2.任意方法的替换
没有lookup方法注射那么有用的另一种方法注射的形式是,用另外的方法实现替换掉被管理bean的任意的方法。用户可以安全地跳过这一节(叙述这个有些高级的特性),除非这个功能确实需要。
在一个XmlBeanFactory中,对于一个被部署的bean,replaced-method元素可以用来把已存在的方法实现替换为其他的实现。考虑如下的class,有一个我们想要重写的computevalue方法:
public class MyvalueCalculator { public String computevalue(String input) { ... some real code } ... some other methods } |
需要为新方法定义提供实现org.springframework.beans.factory.support.MethodReplacer接口的类。
[code]/** 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 ...;
}[/code]
部署原始class和指定方法重写的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元素来完成的。与这种方式不同的是,有时一个知道container的bean仅仅会被给与它的依赖的id(使用一个字符串值或等价的idref元素)。接着第一个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不自动装配。通过使用自动装配,减少(或消除)指定属性(或构造函数参数)的需要,节省显著的键入工作是有可能的。在XmlBeanFactory中,使用bean元素的autowire属性来指定bean定义的自动装配模式。以下是允许的值:
表3.2。自动装配模式
模式 解 释
no 不使用自动装配。Bean的引用必须通过ref元素定义。这是默认的配置,在较大的部署环境中不鼓励改变这个配置,因为明确的指定合作者能够得到更多的控制和清晰性。从某种程度上说,这也是系统结构的文档形式。
byName 通过属性名字进行自动装配。这个选项会会检查BeanFactory,查找一个与将要装配的属性同样名字的bean 。比如,你有一个bean的定义被设置为通过名字自动装配,它包含一个master属性(也就是说,它有一个setMaster()方法),Spring就会查找一个叫做master的bean定义,然后用它来设置master属性。
byType 如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个的情况,则一个致命的异常会被抛出,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check=”objects”属性的值来指定在这种情况下应该抛出错误。
constructor 这个同byType类似,不过是应用于构造函数参数的。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。
autodetect 通过对bean class的内省来选择constructor或byType。如果找到一个缺省的构造函数,那么就会应用byType。
注意:显式的依赖,比如property和construtor-arg元素,总会覆盖自动装配。自动装配的行为可以和依赖检查结合使用,依赖检查会在自动装配完成后发生。
注意:正如我们已经提到过的,对于大型的应用,自动装配不鼓励使用,因为它去除了你的合作类的透明性和结构。
3.3.6.检查依赖
对于要部署到BeanFactory的bean的未解决的依赖,Spring有能力去检查它们的存在性。这些依赖要么是bean的JavaBean式的属性,在bean的定义中并没有个它们设置真实的值,要么是通过自动装配特性被提供。
当你想确保所有的属性(或者某一特定类型的所有属性)都被设置到bean上面的时候,这项特性就很有用了。当然,在很多情况下一个bean class的很多属性都会有缺省的值,或者一些属性并不能应用于所有的应用场景,那么这个特性的作用就有限了 。依赖检查能够分别对每一个bean应用或取消应用,就像自动装配功能一样。缺省的是不检查依赖。依赖检查可以以几种不同的模式处理。在XmlBeanFactory中,这个通过bean定义中的dependency-check属性来指定,这个属性有以下的值:
表3.3。依赖检查模式
模式 解 释
none 不进行依赖检查。没有指定值的bean属性简单地不进行设值。
simple 对基本类型和集合(除了合作者外[比如其他的bean]的所有东西)进行依赖检查。
object 对合作者进行依赖检查。
all 对合作者,基本类型和集合都进行依赖检查。
3.4.自定义bean的自然特性3.4.1.生命周期接口
Spring提供了一些标志接口,用来改变BeanFactory中的bean的行为。它们包括InitailizingBean和DisposableBean。实现这些接口将会导致BeanFactory为调用前一个的afterPropertiesSet(),调用后一个destroy(),从而使得bean可以在初始化和析构后做一些特定的动作。
在内部,Spring使用BeanPostProcessors来处理它能找到的标志接口以及调用适当的方法。如果你需要自定义的特性或者其他的Spring没有提供的生命周期行为,你可以实现自己的BeanPostProcessor。关于这方面更多的内容可以看这里:3.7节,用BeanPostProcessors定制bean。
所有的生命周期的标志接口都在下面叙述。在附录的一节中,你可以找到相应的图,展示了Spring如何管理beans;那些生命周期的特性如何改变你的bean的自然特性以及它们如何被管理。
3.4.1.1.InitializingBean/初始化方法
实现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 } } |
同下面的完全一样,但却不把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } } |
3.4.1.2.DisposableBean/析构方法
实现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="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like closing connection) } } |
和下面的定义是一样的,当时去不会把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work } } |
重要的提示:当以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;
}
这允许beans可以编程式地操控创建它们的BeanFactory,既可以直接使用org.springframework.beans.factory.BeanFactory接口,也可以将引用cast为已知的子类型从而获得更多的功能。这个特性主要用于编程式地取得其他beans。虽然在一些场景下这个功能是有用的,但是一般来说它应该避免使用,因为它使代码与Spring耦合在一起,而且也不遵循反向控制的风格(合作者被当作属性提供给beans)。
3.4.2.2.BeanNameAware
如果一个bean实现了org.springframework.beans.factory.BeanNameAware接口,并且被部署到一个BeanFactory中,那么BeanFactory就会通过这个接口来调用bean来通知这个bean它(bean)被部署的id。这个回调发生在普通的bean属性设置之后,初始化回调比如InitializiongBean的afterPropertiesSet方法(或者自定义的init-method)调用之前。
3.4.3.FactoryBean
接口org.springframework.beans.factory.FactoryBean将会被自己是factory的对象所实现。BeanFactory接口提供了三个方法:
l Ojbect getObject():必须返回一个这个factory创建的对象的实例。这个实例可以使共享的(取决于这个工厂返回的是singleton还是prototype)。
l boolean isSingleton():必须返回true如果Factory返回singletons,否则返回false。
l Class getObjectType():返回getObject()方法返回的对象的类型,或者如果类型不是预先知道的则返回null。
3.5.child bean定义
一个bean定义可能会包含大量的配置信息,包括容器相关的信息(比如初始化方法,静态工厂方法等等)以及构造函数参数和属性的值。一个child bean定义是一个能够从parent bean定义继承配置数据的bean定义。然后它可以覆盖一些值,或者添加一些其他需要的。使用父和子的bean定义可以节省很多的输入工作。实际上,这就是一种模版形式。当编程式地使用一个BeanFactory,child bean定义用ChildBeanDefinitiaon类表示。大多数的用户从来不需要在这个级别上使用它们,取而代之的是,在诸如XmlBeanFactory中声明式地配置bean定义。在一个XmlBeanFactory的bean定义中,使用parent属性指出一个child bean定义,而parent 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> |
如果child bean定义没有指定class,一个child bean定义将使用父定义的class,当然也可以覆盖它。在后面一种情况中,child bean的class必须同parent bean的兼容,比如它必须能够接受父亲的属性值。
一个child bean定义将会从父亲处继承构造函数参数,属性值以及方法,并且可以选择地增加新的值。如果初始化方法,销毁方法和/或静态工厂方法被指定了,它们就会覆盖父亲相应的设置。
剩余的设置将总是从子定义处得到:depends on, autowire mode, dependency check, singleton, lazy init。
在下面的例子中父定义并没有指定一个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> |
这个parent bean就无法自己实例化;它实际上仅仅是一个纯模版或抽象bean,仅仅充当子定义的父定义。若要尝试单独使用这样的parent bean(比如将它作为其他bean的ref属性而引用,或者直接使用这个parent bean的id调用getBean()方法),将会导致一个错误。同样地,容器内部的preInstantiateSingletons方法会完全忽略这种既没有parent也没有class属性的bean定义,因为它们是不完整的。
重要的注意点:这里并没有办法显式地声明一个bean定义为抽象的。如果一个bean确实有一个class定义,那么它就能够被实例化。而且要注意XmlBeanFactory默认地将会预实例化所有的singletons。因此很重要的一点是:如果你有一个(父)bean定义指定了class属性,而你又想仅仅把它当作模板使用,那么你必须保证将lazy-init属性设置为true(或者将bean标记为non-singleton),否则XmlBeanFactory(和其他可能的容器)将会预实例化它。
3.6.同BeanFactory交互BeanFactory本质上不过是高级factory的借口,有能力维护不同beans和它们依赖的注册。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个方法:
l boolean containsBean(String):如果BeanFactory包含一个与所给名称匹配的bean定义则返回true
l Object getBean(String):返回一个以所给名字注册的bean的实例。返回一个singleton的共享的实例还是一个新创建的实例,这取决于bean在BeanFactory配置中如何被配置的。一个BeanException将会在下面两种情况中抛出:bean没有被找到(在这种情况下,抛出的是NoSuchBeanDefinitionException),或者在实例化和准备bean的时候发生异常
l Object getBean(String, Class):返回一个以给定名字注册的bean。返回的bean将会被造型成给定的Class。如果bean不能被转型,相应的异常将会被抛出(BeanNotOfRequiredTypeException)。此外getBean(String)的所有规则也同样适用(见上面)
l boolean isSingleton(String):得到以给定名字注册的bean定义是一个singleton还是一个prototype。如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
l String[] getAliases(String):如果给定的bean名字在bean定义中有别名,则返回这些别名
3.6.1.得到一个FactoryBean而不是它的产品
有些时候我们有必要向BeanFactory请求一个实际的FactoryBean实例本身,而不是它生产出来的bean。在调用BeanFactory(包括ApplicationContext)的getBean方法的时候,在传入的参数bean id前面加一个“&”符号,就可以做到这一点。所以,对于一个给定的id为myBean的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的时候,bean post-processor必须通过类似下面的代码来手动地显示地注册:
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 factory
一个bean factory post-processor是一个实现了org.springframework.beans.factory.config. BeanFactoryPostProcessor接口的类。它将会被手动执行(BeanFactory的时候)或自动执行(ApplicationContext的时候),在BeanFactory构造出来后,对整个BeanFactory做某些类别的改变。Spring包含很多已存在的bean factory post-processor,比如PopertyResourceConfigure和PropertyPlaceHolderConfigurer(这两个将在下面介绍),以及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.PopertyPlaceholderConfigurer
PorpertyPlaceholderConfigurer作为一个bean factory post-processor实现,它可以用来将BeanFactory定义中的属性值放置到另一个单独的Java Properties格式的文件中。这将使用户不用对BeanFactory的主XML定义文件进行复杂和危险的修改,就可以定制一些基本的属性(比如说数据库的urls,用户名和密码)。
考虑一个BeanFactory定义的片断,这里的DataSource以及placeholder值是如下定义的:
在下面这个例子中,一个datasource被定义,并且我们会在一个外部的Porperties文件中配置一些相关属性。在运行时,我们为BeanFactory提供一个PorpertyPlaceholderConfigurer,它将会替换掉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中检查。这个行为能够通过设置configurer的systemPropertiesMode属性来定制。这个属性有三个值,一个让configurer总是覆盖,一个让它从不覆盖,一个让它仅在properties文件中找不到的时候覆盖。请参考PropertiesPlaceholderConfigurer的JavaDoc获得更多信息。
3.8.2.PorpertyOverrideConfigurer
另一个bean factory post-processor:PropertyOverrideConfigurer类似于PropertyPlaceholderConfiguer,但是与后者相比,前者的定义对于bean属性可以有缺省值或者根本没有值。如果相对重要的Properties文件没有包含一个bean属性对应的条目,那么缺省的上下文定义将被使用。
注意:bean factory的定义并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻明显地知道override configurer是否被使用了。在有多个PorpertyOverrideConfigurer对用一个bean属性定义了不同的值的时候,最后一个将取胜(取决于覆盖的机制)。
Properties文件的配置行应该是如下的格式:
beanName.property=value
一个properties文件的例子将会是下面这样的:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
如果一个BeanFactory包含了一个叫做dataSource的bean,这个bean又有driver和url属性,那么这个实例文件在这个BeanFactory中就可用。
3.9.注册附加的定制PropertyEditor
当用字符串值设置bean的属性时,BeanFactory实质上使用了标准的JavaBeans 的PropertyEditor将这些String转换为属性的复杂类型。Spring预先注册了很多定制的PropertyEditor(比如,将一个字符串表示的classname转换成真正的Class对象)。另外,一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么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的支持类,ContextLoader能够自动启动一个ApplicationContext并把这个过程作为J2EE web应用的普通启动进程的一部分。当然,这种情况下还是可以编程式地创建一个ApplicationContext。
Context包的基础是ApplicationContext接口,位于org.springframework.context包中。它是由BeanFactory衍生而来,提供BeanFactory所有的功能。为了以一种更框架导向的方式工作,使用分层和分等级的context,context包还提供如下:
l MessageSource,提供对i18n信息的访问
l 对资源的访问,比如URL和文件
l 事件传播向实现了ApplicationListener接口的bean
l 装载多个(分级的)context,允许每一个专注于一个特定的层次,比如应用的web层
因为ApplicationContext包括了BeanFactory所有的功能,所以通常建议先于BeanFactory使用,除了很少的一些限定场合比如在一个Applet中,内存的消耗是决定性的,几个额外kb都会产生不同的结果。接下来的章节将叙述ApplicationContext在BeanFactory的基本能力上增建的功能。
3.11.ApplicationContext增加的功能
正如上面说明的那样,ApplicationContext有几个区别于BeanFactory的特性。让我们一个一个地观察它们。
3.11.1.使用MessageSource
ApplicationContext接口继承自MessageSource接口,所以提供了信息化(i18n或者国际化)功能。同NestingMessageSource一起使用,就能够处理分等级的信息,这些是Spring提供的处理信息的基本接口。让我们快速地浏览一下这里定义的方法:
l String getMessage(String code, Object[] args, String default, Locale loc):从MessageSource取得信息的基本方法。如果对于指定的区域没有找到信息,则使用默认的信息(参数default)。传进去的参数args是做为信息中那些要被替换的值,这个是通过Java标准类库的MessageFormat实现的。
l String getMessage(String code, Object[] args, Locale loc):本质上和上一个方法是一样的,但是有一点区别:没有制定默认值;如果信息找不到,就会抛出一个NoSuchMessageException。
l String getMessage(MessageSourceResolveable resolvable, Locale locale):上面两个方法使用的所有属性都是封装到一个叫做MessageSourceResolvable的类中,你可以通过这个方法直接使用它。
当ApplicationContext被加载的时候,它会自动查找在context中定义的MessageSource bean。这个bean必须叫做messageSource。如果找到了这样的一个bean,所有对上述方法的调用将会被委托给这个找到的message source。如果没有找到message source,ApplicationContext将会尝试查它的parent是否包含这个名字的bean。如果有,它将会把找到的bean作为MessageSource。如果它最终没有找到任何的信息源,一个空的StaticMessageSource将会被实例化,为了能够接受对上述方法的请求。
Spring目前提供了两个MessageSource的实现。它们是ResourceBundleMessageSource和StaticMessageSource。两个都实现了NestingMessageSource以便能够嵌套信息。StaticMessageSource除了提供编程式的方式向source增加message外,几乎不会被用到。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,分别叫做format, exceptions和windows。使用JDK的标准做法:通过ResourceBundle处理信息,任何处理信息的请求都会被处理。TODO:举一个例子
3.11.2.传播事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口来提供的。如果context中部署了一个实现了ApplicationListener接口的bean,每次一个ApplicationEvent发布到ApplicationContext时,那个bean就会被通知。实质上,这是标准的Observer设计模式。Spring提供了三个标准事件:
表3.4。内建的事件
事件 解 释
ContextRefreshedEvent 当ApplicationContext已经实例化或刷新后发布的事件。这里实例化意味着:所有的bean被装载,singleton被预实例化,以及ApplicationContext准备好可用
ContextClosedEvent 当使用ApplicationContext的close()方法结束context的时候发布的事件。这里结束意味着:singleton被销毁
RequestHandledEvent 一个与web相关的事件,告诉所有的bean一个HTTP请求已经被响应了(这个事件将会在一个请求结束后被发布)。注意,这个事件只能应用于使用了Spring DispatcherServlet的wen应用
同样也可以实现自定义的事件。通过调用ApplicationContext的publishEvent()方法,并且指定一个参数,这个参数是你自定义的event class的一个实例。我们来看一个例子。首先是ApplicationContext:
<bean id="emailer" class="example.EmailBean"> <property name="blackList"> <list> <value>black@list.org</value> <value>white@list.org</value> <value>john@doe.org</value> </list> </property> </bean> <bean id="blackListListener" class="example.BlackListNotifier"> <property name="notificationAddress"> <value>spam@list.org</value> </property> </bean> |
然后是实际的bean:
public class EmailBean implements ApplicationContextAware { /** the blacklist */ private List blackList; public void setBlackList(List blackList) { this.blackList = blackList; } public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent evt = new BlackListEvent(address, text); ctx.publishEvent(evt); return; } // send email } }
public class BlackListNotifier implement ApplicationListener {
/** notification address */ private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; }
public void onApplicationEvent(ApplicationEvent evt) { if (evt instanceof BlackListEvent) { // notify appropriate person } } } |
当然,这个特定的例子或许可以用更好的方式实现(或许使用AOP特性),但是它还是足够说明基本的事件机制。
3.11.3.在Spring中使用资源
很多应用程序都需要访问资源。资源可以包括文件,以及其他如web页面或NNTP newsfeeds的东西。Spring提供了一个清晰透明的方案,以一种协议无关的方式访问资源。ApplicationContext接口包含一个方法负责这项工作。(getResource(String))
Resource类定义了几个方法,这几个方法被所有的Resource实现所共享:
表3.5。资源功能
方法 解 释
getInputStream() 在资源上打开一个InputStream,并返回这个InputStream。
exists() 验证资源时候存在,如果不存在返回false。
isOpen() 如果这个资源不能打开多个流将会返回true。对于除了基于文件的资源之外的一些资源会返回false,这些资源不能同时被多个流读取。
getDescription() 返回资源的描述,通常是全限定名或者实际的URL。
Spring提供了几个Resource的实现。它们都需要一个String表示的实际的资源位置。依据这个String,Spring将会自动为你选择正确的Resource实现。当向ApplicationContext请求一个资源时,Spring首先检查你指定的资源位置,寻找任何前缀。取决于ApplicationContext的实现,不同的Resource将会是可用的。Resource最好是使用ResourceEditor来配置,比如XmlBeanFactory。
3.12.ApplicationContext中的用户行为
BeanFactory已经提供了许多机制用来控制部署在其中的bean的生命周期(比如标志接口InitializingBean或DisposableBean,它们的结构和XmlBeanFactory配置中的init-method和destroy-method属性以及bean post-processor是相同的。在ApplicationContext中,这些也同样可以工作,但同时也增加了一些用于定制beans和容器行为的机制。
3.12.1.ApplicationContextAware标志接口
所有BeanFactory中可用的标志接口这里也可以使用。ApplicationContext又增加了一个bean可以实现的标志接口:org.springframework.context.ApplicationContextAware。如果一个bean实现了这个接口并且被部署到了context中,当这个bean创建的时候,将使用这个接口的setApplicationContext()方法回调这个bean,为这个bean提供一个指向当前context的引用,这个引用将被存储起来以便bean后面与context发生交互。
3.12.2.BeanPostProcessor
Bean post-processor(实现了org.springframework.beans.factory.config. BeanPostProcessor的java类)在上面已经叙述过了。这里还是值得再次提到,post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一个ApplicationContext中,一个实现了上述接口的部署的bean将会被自动查找并且作为一个bean post-processor被注册,在factory中的每一个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一样部署的时候context会自动识别和应用任何的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.注册额外的定制的PropertyEditor
正如前面曾提到的那样,Spring使用标准的JavaBeans PropertyEditor将字符串形式表示的属性值转换为属性真实的复杂的类型。CustomEditorConfigurer(一个特殊的bean factory post-processor)可以使ApplicationContext方便地增加对额外的PropertyEditor的支持。考虑一个用户类ExoticType,以及另外一个需要ExoticType作为属性的DependsOnExotic的类:
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上,然后就可以在需要的时候使用这个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,可以返回一个静态或非静态方法的调用结果。
下面是一个bean定义的例子,这个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方式进行操作。经由容器的第一次让factory生产对象的请求将会导致相应的那个工厂方法的调用,这个方法的返回值将会被缓存并且返回给当次请求和后来的请求。这个factory内部的singleton属性可以被设置为false,从而导致每次对象的请求都会调用那个目标方法。
通过给targetMethod属性设置一个静态方法名字的字符串来指定静态目标方法,而targetClass则用来指定定义静态方法的类。同样地,也可以指定一个目标实例方法,通过targetObject属性设置目标对象,targetMethod属性设置要在目标对象上调用的方法的名字。方法调用的参数可以通过设置args属性来指定。
3.15.在一个web应用中创建一个ApplicationContext
与BeanFactory总是被编程式地创建相比,ApplicationContext可以通过使用诸如ContextLoader而声明式地被创建。当然你也可以用ApplicationContext的任一种实现来编程式地创建它。首先,我们来看看ContextLoader以及它的实现。
ContextLoader有两个实现:ContextLoaderListener和ContextLoaderServlet。它们两个有着同样的功能,区别是listener不能在兼容Servlet2.2的容器中使用。自从Servelt2.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。如果它存在,它就会将那个String用预先定义的分隔符(逗号,分号和空格)分开并将这些值作为application context将要搜索的位置。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方案代理到一个普通的java实现的对象,这个对象由BeanFactory产生。虽然理想地BeanFactory不需要是一个singleton,但是如果每个bean使用它自己的non-singleton的BeanFactory,由于内存使用或初始化时间这将变得不切实际。
另一个例子,在一个多层的复杂的J2EE应用中(比如有很多JAR,EJB,以及WAR打包成一个EAR),每一层都有自己的ApplicationContext定义(有效地组成一个层次结构),如果顶层只有一个web-app(WAR)的话,比较好的做法是创建一个由不同层的XML定义文件组成
在Spring中,两个最基本最重要的包是:org.springframework.beans和org.springframework.context包。这两个包中的代码为Spring的反向控制特性(也叫作依赖注射)提供了基础。BeanFactory[http://www.springframework.org/docs/api/org/ ;springframework/beans/factory/BeanFactory.html]提供了能够管理任何种类beans(对象)的先进的配置机制,潜在地利用任何一种存储设备。ApplicationContext建立在BeanFactory之上并增加了其他的功能,比如同Spring AOP特性更容易整合,信息资源处理(用于国际化),事件传播,声明式机制用于创建ApplicationContext和可选的父上下文以及与应用层相关的上下文(比如WebApplicationContext),以及其他的增强。
简而言之,BeanFactory提供了配置框架和基本的功能,而ApplicationContext为它增加了更强的功能,这些功能中的一些或许更加J2EE和企业中心(enterprise-centric)。一般来说,ApplicationContext是BeanFactory的完全超集,任何BeanFactory功能和行为的描述也同样被认为适用于ApplicationContext。
用户有时在特定的场合下不确定BeanFactory和ApplicationContext哪一个更适于使用。通常大部分在J2EE环境中构建的应用最好的选择是使用ApplicationContext,因为它不仅提供了BeanFactory所有的特性以及它自己附加的特性,而且还提供更声明化的方法去使用一些功能,这通常是令人满意的。你最好选择BeanFactory的主要场景通常是当内存使用是最主要的关注(比如在一个每kb都要计算的applet中),而且你也不需要ApplicationContext所有特性的时候。
这一章粗略地分为两部分,第一部分包括同时适用于BeanFactory和ApplicationContext的基本原则。第二部分会包括仅仅适用于ApplicationContext的一些特性。
3.2.BeanFactory和Bean定义(基础)
3.2.1.BeanFactory
BeanFactory是实际上实例化,配置和管理许多beans的容器。这些beans通常互相之间合作,因而也在它们之间产生依赖。这些依赖反映在被BeanFactory使用的配置数据中(一些依赖可能不像配置数据一样可见,而更可能在运行期作为bean之间程序交互的函数)。
一个BeanFactory用接口org.springframework.beans.factory.BeanFactory表示,这个接口有多个实现。最常使用的的简单的BeanFactory实现是org.springframework.beans.factory.xml.XmlBeanFactory。(这带有如下的暗示:ApplicationContext是BeanFactory的子类,所以大多数的用户更喜欢使用ApplicationContext的XML形式)。
尽管对于大多数场景,几乎所有的被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层提供支持代码,用来自动读取一个Spring ApplicationContext并把读取过程作为一个J2EE web应用启动过程的一部分。这个声明式的过程在这里描述:
编程式地操纵BeanFactory将会在后面接受,下面部分将集中描述BeanFactory的配置。
在最基础的级别上,一个BeanFactory配置由一个或多个BeanFactory必须管理的Bean的定义组成。在一个XmlBeanFactory中,在顶级的<beans>元素中配置一个或多个<bean>元素。
3.2.2.Bean定义
一个XmlBeanFactory实体中的Bean定义包括如下的细节以及其他一些信息:
l 一个classname:通常这是Bean定义中描述的那个bean的真正的实现类。然而如果一个bean使用一个静态工厂方法所创建而不是被普通的构造函数创建,那么这里实际上就是工厂类的classname。
l Bean行为配置元素:声明这个bean在容器的行为方式(比如prototype或singleton,自动装配模式,依赖检查模式,初始化和析构方法)。
l 构造函数参数和需要赋予新创建bean的属性:举一个例子,一个管理连接池的bean使用的连接数目(即可以指定为一个property也可以作为一个构造函数参数),或者池大小的限制数目。
l 这个bean需要起作用的其他beans:比如它的合作者(同业也可以作为属性或者构造函数的参数)。这个也被叫做依赖。
上面列出的概念直接转化为组成bean定义的一组元素。这些元素在下面的表格中列出,关于它们每一个的更详细的说明都有一个链接相对应。
表3.1。Bean定义说明
特性 更多信息
class 3.2.3节,bean class
id和name 3.2.4节,bean标志符(id和name)
singleton或prototype 3.2.5节,使用singleton还是不使用呢
构造函数参数 3.3.1节,设置bean的属性和合作者
bean属性 3.3.1节,设置bean的属性和合作者
自动装配模式 3.3.5节,自动装配合作者
依赖检查模式 3.3.6节,检查依赖
初始化方法 3.4.1节,生命周期接口
析构方法 3.4.1节,生命周期接口
注意bean定义是由真实的接口org.springframework.beans.factory.config.BeanDefinition以及它的各种子接口和实现所表示的。然而,绝大多数的用户代码不需要与BeanDefination直接作用。
3.2.3.bean class
class属性通常是强制性的(参看3.2.3.3节-通过实例工厂方法创建bean和3.5节-child bean的定义这两个例外情况),被用来达到两个目的中的一个。在绝大多数普通的场景中BeanFactory自己直接调用bean的构造函数来创建bean(相当于调用new的Java代码),class属性指定了需要创建的bean的类。在比较少的普通场景中BeanFactory调用某个类的静态的所谓的工厂方法来创建bean,class属性指定了实际包含静态工厂方法的那个类。(至于静态工厂方法返回的bean的类型是同一个类还是完全不同的另一个类,这并不重要)。
3.2.3.1.通过构造方法创建bean
当使用构造函数方式创建bean的时候,所有普通的类都可以被Spring使用以及同Spring兼容。这就是说,被创建的类不需要实现任何指定的接口或者按照特定的样式进行编码。仅仅指定bean的类就足够了。然而,由于依赖你给特定bean使用的IoC类型,你可能需要一个默认的(空的)构造函数。
另外,BeanFactory并不局限于管理真正的JavaBeans,事实上它也能够管理任何你想让它帮你管理的类。虽然很多使用Spring的人喜欢在BeanFactory中用真正的JavaBeans(仅包含一个默认的(无参数的)构造方法以及在属性后面包含适当的相对应的setter和getter),但是在你的BeanFactory中也可以使用特殊的非bean样式的类。举例来说,如果你需要使用一个遗留下来的完全没有遵守JavaBean规范的连接池,不要担心,Spring同样能够管理它。
使用XmlBeanFactory你可以像如下这样制定你的bean class:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/> |
至于为构造方法提供参数(可选的),以及对象实例构造后设置实例的属性,将会在后面叙述。
3.2.2.2.通过静态工厂方法创建Bean
当你定义一个使用静态工厂方法创建的bean,同时使用class属性指定包含静态工厂方法的类,这个时候就需要另一个叫做factory-method的属性来指定工厂方法的名字。Spring期待能够调用这个方法(包含可选的一组参数将在后面叙述)并且返回一个有效的对象,之后这个对象就会如同用构造方法创建的对象一样被看待。用户可以使用这样的bean定义在遗留代码中调用静态工厂。
下面是一个bean定义的例子,声明了一个bean要通过调用一个工厂方法被创建。要注意定义并没有指定返回对象的类型,只指定包含工厂方法的类。在这个例子中,createInstance必须是静态方法。
<bean id="exampleBean" class="examples.ExampleBean2" factory-method="createInstance"/> |
至于为工厂方法提供参数(可选的),以及对象实例被工厂方法返回后设置该实例的属性,将会在后面叙述。
3.2.3.3.通过实例工厂方法创建bean
与使用静态工厂方法创建bean非常类似的是,使用一个实例工厂方法(非静态的),通过调用一个已存在bean(这个bean应该是工厂类型)的工厂方法来创建新的bean。
要使用这种机制,class属性必须保留为空的,而且factory-bean属性必须指定一个包含工厂方法的bean的name(¥¥)。而工厂方法本身仍然要通过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属性的机制将在后面讨论,这个方案有一个暗示就是factory bean本身能够被容器通过依赖注射来管理和配置。
3.2.4.bean的标志符(id和name)
每一个bean都有一个或多个ids(也叫作标志符,或names;这些名词说的是一回事)。这些id在bean被管理的BeanFactory或ApplicationContext中必须是唯一的。一个bean差不多总是只有一个id,但是如果一个bean有超过一个的id,那么另外的那些本质上可以认为是别名。在一个XmlBeanFactory中(包括ApplicationContext的各种形式),你可以用id或者name属性来指定bean的id(s),并且在这两个或其中一个属性中至少指定一个id。id属性允许你指定一个id,并且它在XML DTD(定义文档)中作为一个真正的XML元素的ID属性被标记,所以parser能够在其他元素指回向它的时候做一些额外的校验。正因如此,用id属性指定bean的id是一个更好的方式。然而,XML规范严格限定了在XML ID中合法的字符。通常这并不是一个实在的约束,但是如果你有必要使用这些字符(在ID中的非法字符)中的一个,或者你想给bean引进其他的别名,那么你可以通过name属性指定一个或多个ids(用逗号“,”或者分号“;”分开)。
3.2.5.使用singleton还是不使用呢
Beans被定义为两种部署模式中的一种:singleton或non-singleton。(后一种也别叫作prototype,尽管这个名词用的不精确因为它并不是非常适合)。如果一个bean是singleton的,那么仅有一个共享的这个bean的实例被管理,所有id或者ids与这个bean定义匹配的beans请求都会导致那个唯一特定的bean实例被返回。
而non-singleton,prototype模式的bean部署产生的后果是:对这个bean的每次请求都会创建一个新的bean实例。这对于例如每个user需要一个独立的user对象这样的情况是非常理想的。
Beans默认被部署为singleton模式,除非你指定为另外的。要紧记把类型变为non-singletion(prototype)后,每一次对这个bean的请求都会导致一个新创建的bean而且这可能并不是你真正想要的。所以仅仅在绝对需要的时候才把模式改成prototype的。
在下面这个例子中,两个bean一个被定义为singleton的而另一个被定义为non-singleton(prototype)的。exampleBean会在客户端每次向BeanFactory请求它的时候被创建,而yetAnotherExample仅仅被创建一次;在对它每次请求的时候这个实例的引用会被返回。
<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之后,真正地注射这些依赖。This is fundamentally the inverse (hence the name Inversion of Control) of the bean instantiating or locating its dependencies on its own using direct construction of classes,就像ServiceLocator模式一样。我们不会详细阐述依赖注射的优点,很显然通过使用它:代码变得非常清晰;当beans不再自己查找他们的依赖而是由容器提供,甚至不需要知道在何处定位依赖以及依赖实际上是什么类型,这时高层次的解耦也变得很容易了。
正如上面提到的那样,反向控制/依赖注射存在两种主要的形式:
l 基于setter的依赖注射,是在调用无参的构造函数或无参的静态工厂方法实例化你的bean之后,通过在你的bean上调用setter实现的。在BeanFactory中定义的使用基于setter的注射依赖的bean是真正的JavaBeans。Spring一般提倡使用基于setter的依赖注射,因为很多的构造函数参数将会是笨重的,尤其在有些属性是可选的情况下。
l 基于构造函数的注射依赖,它是通过调用带有许多参数的构造方法实现的,每个参数表示一个合作者或者属性。另外,调用带有特定参数静态工厂方法来构造bean可以被认为差不多等同的,接下来的文字会把构造函数的参数和静态工厂方法的参数看作一样的。虽然Spring一般提倡在大多数情况下使用基于setter的依赖注射,但是Spring还是完全支持基于构造函数的依赖注射,因为你可能想要把它用在那些只提供多参数构造函数并且没有setter的遗留的beans上。另外对于一些比较简单的beans,一些人更喜欢使用构造函数方法以确保beans不会被以错误的形式构造。
BeanFactory同时支持这两种将依赖注射到被管理bean中的方式。(实际上它还支持在一些依赖已经通过构造函数方法注射后再使用基于setter的依赖注射)。依赖的配置是以BeanDefinition的形式出现,它和JavaBeans的PropertyEditors一起使用来了解如何把properties从一个格式转变为另一个。真正传送的值被变为Propertyvalue对象的形式。然而,大多数Spring的使用者并不要直接(比如编程式地)处理这些classes,而更多地使用一个XML定义文件,这个文件会在内部被转变为这些classes的实例,用来读取整个BeanFactory或ApplicationContext。
Bean依赖的决定通常按照下面所示发生:
l BeanFactory通过使用一个描述所有beans的配置被创建和实例化。大多数的Spring用户使用一个支持XML格式配置文件的BeanFactory或ApplicationContext变体。
l 每一个bean的依赖都是属性的形式,构造函数参数的形式,或者当用静态工厂方法代替普通构造函数时工厂方法的参数。这些依赖将会在bean真正被创建出来后提供给bean。
l 每一个属性或者构造函数参数要么是一个要被设置的值的真实定义,要么是一个指向BeanFactory中其他bean的引用。在ApplicationContext的情况下,这个引用可以指向一个父亲ApplicationContext中bean。
l 每一个是真实值的属性或构造函数参数,必须能够从(配置文件中)被指定的格式转变为属性或构造函数参数的真实类型。默认的Spring能够把一个字符串格式的值转变为所有内建的类型,比如int, long, String, boolean等等。另外当说到基于XML的BeanFactory变体的时候(包括ApplicationContext变体),它们已经为定义Lists, Maps, Sets和Properties集合类型提供了内在的支持。另外,Spring通过使用JavaBeans的PropertyEditor定义从而能够将字符串值转变为其他任意的类型。(你可以为BeanFactory提供你自己的PropertyEditor定义从而能够转变你自定义的类型;更多关于PropertyEditors的信息以及如何手动增加自定义的PropertyEditors请参看3.9节,注册附加的自定义PropertyEditors)。当一个bean属性是一个Java Class类型,Spring允许你用这个class的名字的字符串作为这个属性的值,ClassEditor这个内建的PropertyEditor会帮你把class的名字转变成真实的Class实例。
l 很重要的一点就是要了解:Srping在BeanFactory创建的时候要校验BeanFactory中每一个Bean的配置。这些校验包括作为Bean引用的属性必须实际引用一个合法的beans(比如被引用的bean也定义在BeanFactory中,或者当ApplicationContext时,在父亲上下文中)。但是,bean属性本身直到bean被真实建立的时候才被设置。对于那些是singleton并且被设置为pre-instantiated的bean来说(比如一个ApplicationContext中的singleton bean),Bean创建发生在BeanFactory被创建的时候,但是对于其他情况,发生在bean被请求的时候。当一个bean必须被创建时,它会潜在地导致一系列的其他bean被创建,像它的依赖以及它的依赖的依赖(如此下去)被创建和赋值。
l 通常你可以信任Spring做了正确的事情。它会在BeanFactory装载的时候检查处错误,包括对不存在bean的引用和循环引用。它会尽可能晚地真正设置属性和解决依赖(比如创建那些需要的依赖),在bean真正被创建的时候。这就意味着:就算一个BeanFactory被正确地装载,稍后当你请求一个bean的时候,如果创建那个bean或者它的依赖的时候出现了错误,这个BeanFactory也会生成一个异常。比如,如果一个bean抛出一个异常作为缺少或非法属性的结果,这样的情况就会发生。这种潜在地推迟一些配置错误可见性的行为正是ApplicationContext默认预实例化singleton beans的原因。以前期的时间和内存为代价在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的构造函数参数被传入。现在考虑一下替换掉构造函数的变体,Spring被告知调用一个静态工厂方法来返回一个对象的实例:
<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元素被支持,构造函数事实上也是一样的被使用。这些参数是可选的。很重要的一点是工厂方法所返回的class类型不一定要和包含着个静态工厂方法的class一致,虽然上面这个例子中是一样的。前面所提到的实例工厂方法(non-static)用法基本上是一样的(除了使用factory-bean属性代替class属性),在这里就不再详细叙述了。
3.3.2.深入Bean属性和构造函数参数
正如前面提到的那样,bean的属性和构造函数参数可以被定义为其他managed beans的引用(合作者),或者内联定义的值。为了达到这个目的,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的id 或name的字符串来设置属性。
<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属性的class仅仅会在这个class被Spring实例化的时候做它自己的验证,很可能在容器真正部署完很久之后。
另外,如果被引用的bean在同一个xml文件中而且bean的名称是bean的id,那么local属性就可以被使用。它会让XML parser自己更早地验证bean的名称,在XML文档解析的时候。
<property name="targetName"> <idref local="theTargetBean"/> </property> |
ref元素是最后一个能在property元素中使用的元素。它是用来给一些特定的属性设值的,这些属性是容器管理的其他bean的引用(可以叫做合作者)。正如前一节提到的,被引用的bean被认为是那些属性要被设置的bean的依赖,被应用的bean将会在属性设置之前需要时(如果是一个singleton bean可能已经被容器初始化),一经请求就被初始化。所有的引用根本上就只是一个指向其他对象的引用,不过这里其它对象的id/name如何被指定有3种形式,这3种不同形式决定scoping和验证如何被处理。
用ref标签的bean属性指定目标bean是最常见的形式,对于同一个BeanFactory/ ApplicationContext(无论是否在同一个XML文件中)中,或者parent BeanFactory/ ApplicationContext中的任何bean,允许创建指向它的bean。bean属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中任何一个值相同。
<ref bean="someBean"/>
用local属性指定目标bean可以利用XML parser的能力在同一个文件中验证XML id引用。local属性的值必须与目标bean的id属性一致。如果在同一个文件中没有匹配的元素,XML parser将会产生一个错误。因此,如果目标bean在同一个XML文件中,那么使用local形式将是最好的选择(为了能够尽可能早的发现错误)。
<ref local="someBean"/>
用parent属性指定目标bean允许一个引用指向当前BeanFactory(ApplicationContext)的parent BeanFactory(ApplicationContext)中的bean。Parent属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中的一个值相同,而且目标bean必须在当前BeanFactory(ApplicationContext)的parent中。当需要用某些proxy包装一个parent上下文中存在的bean(可能和parent中的有同样的name),所以需要原始的对象用来包装它。
<ref parent="someBean"/>
3.3.3.方法注射
对于大部分的用户来说,容器中多数的beans将会是singletons。当一个singleton bean需要同另一个singleton bean合作(使用),或者一个non-singleton bean需要同另一个non-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库在客户端的class之上修改二进制码,从而实现上述的场景要求。
在客户端的class包含需要注入的方法,方法定义必须是如下形式的抽象(具体)定义:
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(不管是明确指出的还是默认的)而部署,同一个singleShotHelper实例将会每次被返回。
Lookup方法注射能够同构造函数注射结合(对创建的bean提供可选的构造函数参数),也可以同setter注射结合(在创建的bean之上设置属性)。
3.3.3.2.任意方法的替换
没有lookup方法注射那么有用的另一种方法注射的形式是,用另外的方法实现替换掉被管理bean的任意的方法。用户可以安全地跳过这一节(叙述这个有些高级的特性),除非这个功能确实需要。
在一个XmlBeanFactory中,对于一个被部署的bean,replaced-method元素可以用来把已存在的方法实现替换为其他的实现。考虑如下的class,有一个我们想要重写的computevalue方法:
public class MyvalueCalculator { public String computevalue(String input) { ... some real code } ... some other methods } |
需要为新方法定义提供实现org.springframework.beans.factory.support.MethodReplacer接口的类。
[code]/** 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 ...;
}[/code]
部署原始class和指定方法重写的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元素来完成的。与这种方式不同的是,有时一个知道container的bean仅仅会被给与它的依赖的id(使用一个字符串值或等价的idref元素)。接着第一个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不自动装配。通过使用自动装配,减少(或消除)指定属性(或构造函数参数)的需要,节省显著的键入工作是有可能的。在XmlBeanFactory中,使用bean元素的autowire属性来指定bean定义的自动装配模式。以下是允许的值:
表3.2。自动装配模式
模式 解 释
no 不使用自动装配。Bean的引用必须通过ref元素定义。这是默认的配置,在较大的部署环境中不鼓励改变这个配置,因为明确的指定合作者能够得到更多的控制和清晰性。从某种程度上说,这也是系统结构的文档形式。
byName 通过属性名字进行自动装配。这个选项会会检查BeanFactory,查找一个与将要装配的属性同样名字的bean 。比如,你有一个bean的定义被设置为通过名字自动装配,它包含一个master属性(也就是说,它有一个setMaster()方法),Spring就会查找一个叫做master的bean定义,然后用它来设置master属性。
byType 如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个的情况,则一个致命的异常会被抛出,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check=”objects”属性的值来指定在这种情况下应该抛出错误。
constructor 这个同byType类似,不过是应用于构造函数参数的。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。
autodetect 通过对bean class的内省来选择constructor或byType。如果找到一个缺省的构造函数,那么就会应用byType。
注意:显式的依赖,比如property和construtor-arg元素,总会覆盖自动装配。自动装配的行为可以和依赖检查结合使用,依赖检查会在自动装配完成后发生。
注意:正如我们已经提到过的,对于大型的应用,自动装配不鼓励使用,因为它去除了你的合作类的透明性和结构。
3.3.6.检查依赖
对于要部署到BeanFactory的bean的未解决的依赖,Spring有能力去检查它们的存在性。这些依赖要么是bean的JavaBean式的属性,在bean的定义中并没有个它们设置真实的值,要么是通过自动装配特性被提供。
当你想确保所有的属性(或者某一特定类型的所有属性)都被设置到bean上面的时候,这项特性就很有用了。当然,在很多情况下一个bean class的很多属性都会有缺省的值,或者一些属性并不能应用于所有的应用场景,那么这个特性的作用就有限了 。依赖检查能够分别对每一个bean应用或取消应用,就像自动装配功能一样。缺省的是不检查依赖。依赖检查可以以几种不同的模式处理。在XmlBeanFactory中,这个通过bean定义中的dependency-check属性来指定,这个属性有以下的值:
表3.3。依赖检查模式
模式 解 释
none 不进行依赖检查。没有指定值的bean属性简单地不进行设值。
simple 对基本类型和集合(除了合作者外[比如其他的bean]的所有东西)进行依赖检查。
object 对合作者进行依赖检查。
all 对合作者,基本类型和集合都进行依赖检查。
3.4.自定义bean的自然特性3.4.1.生命周期接口
Spring提供了一些标志接口,用来改变BeanFactory中的bean的行为。它们包括InitailizingBean和DisposableBean。实现这些接口将会导致BeanFactory为调用前一个的afterPropertiesSet(),调用后一个destroy(),从而使得bean可以在初始化和析构后做一些特定的动作。
在内部,Spring使用BeanPostProcessors来处理它能找到的标志接口以及调用适当的方法。如果你需要自定义的特性或者其他的Spring没有提供的生命周期行为,你可以实现自己的BeanPostProcessor。关于这方面更多的内容可以看这里:3.7节,用BeanPostProcessors定制bean。
所有的生命周期的标志接口都在下面叙述。在附录的一节中,你可以找到相应的图,展示了Spring如何管理beans;那些生命周期的特性如何改变你的bean的自然特性以及它们如何被管理。
3.4.1.1.InitializingBean/初始化方法
实现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 } } |
同下面的完全一样,但却不把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } } |
3.4.1.2.DisposableBean/析构方法
实现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="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like closing connection) } } |
和下面的定义是一样的,当时去不会把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work } } |
重要的提示:当以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;
}
这允许beans可以编程式地操控创建它们的BeanFactory,既可以直接使用org.springframework.beans.factory.BeanFactory接口,也可以将引用cast为已知的子类型从而获得更多的功能。这个特性主要用于编程式地取得其他beans。虽然在一些场景下这个功能是有用的,但是一般来说它应该避免使用,因为它使代码与Spring耦合在一起,而且也不遵循反向控制的风格(合作者被当作属性提供给beans)。
3.4.2.2.BeanNameAware
如果一个bean实现了org.springframework.beans.factory.BeanNameAware接口,并且被部署到一个BeanFactory中,那么BeanFactory就会通过这个接口来调用bean来通知这个bean它(bean)被部署的id。这个回调发生在普通的bean属性设置之后,初始化回调比如InitializiongBean的afterPropertiesSet方法(或者自定义的init-method)调用之前。
3.4.3.FactoryBean
接口org.springframework.beans.factory.FactoryBean将会被自己是factory的对象所实现。BeanFactory接口提供了三个方法:
l Ojbect getObject():必须返回一个这个factory创建的对象的实例。这个实例可以使共享的(取决于这个工厂返回的是singleton还是prototype)。
l boolean isSingleton():必须返回true如果Factory返回singletons,否则返回false。
l Class getObjectType():返回getObject()方法返回的对象的类型,或者如果类型不是预先知道的则返回null。
3.5.child bean定义
一个bean定义可能会包含大量的配置信息,包括容器相关的信息(比如初始化方法,静态工厂方法等等)以及构造函数参数和属性的值。一个child bean定义是一个能够从parent bean定义继承配置数据的bean定义。然后它可以覆盖一些值,或者添加一些其他需要的。使用父和子的bean定义可以节省很多的输入工作。实际上,这就是一种模版形式。当编程式地使用一个BeanFactory,child bean定义用ChildBeanDefinitiaon类表示。大多数的用户从来不需要在这个级别上使用它们,取而代之的是,在诸如XmlBeanFactory中声明式地配置bean定义。在一个XmlBeanFactory的bean定义中,使用parent属性指出一个child bean定义,而parent 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> |
如果child bean定义没有指定class,一个child bean定义将使用父定义的class,当然也可以覆盖它。在后面一种情况中,child bean的class必须同parent bean的兼容,比如它必须能够接受父亲的属性值。
一个child bean定义将会从父亲处继承构造函数参数,属性值以及方法,并且可以选择地增加新的值。如果初始化方法,销毁方法和/或静态工厂方法被指定了,它们就会覆盖父亲相应的设置。
剩余的设置将总是从子定义处得到:depends on, autowire mode, dependency check, singleton, lazy init。
在下面的例子中父定义并没有指定一个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> |
这个parent bean就无法自己实例化;它实际上仅仅是一个纯模版或抽象bean,仅仅充当子定义的父定义。若要尝试单独使用这样的parent bean(比如将它作为其他bean的ref属性而引用,或者直接使用这个parent bean的id调用getBean()方法),将会导致一个错误。同样地,容器内部的preInstantiateSingletons方法会完全忽略这种既没有parent也没有class属性的bean定义,因为它们是不完整的。
重要的注意点:这里并没有办法显式地声明一个bean定义为抽象的。如果一个bean确实有一个class定义,那么它就能够被实例化。而且要注意XmlBeanFactory默认地将会预实例化所有的singletons。因此很重要的一点是:如果你有一个(父)bean定义指定了class属性,而你又想仅仅把它当作模板使用,那么你必须保证将lazy-init属性设置为true(或者将bean标记为non-singleton),否则XmlBeanFactory(和其他可能的容器)将会预实例化它。
3.6.同BeanFactory交互BeanFactory本质上不过是高级factory的借口,有能力维护不同beans和它们依赖的注册。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个方法:
l boolean containsBean(String):如果BeanFactory包含一个与所给名称匹配的bean定义则返回true
l Object getBean(String):返回一个以所给名字注册的bean的实例。返回一个singleton的共享的实例还是一个新创建的实例,这取决于bean在BeanFactory配置中如何被配置的。一个BeanException将会在下面两种情况中抛出:bean没有被找到(在这种情况下,抛出的是NoSuchBeanDefinitionException),或者在实例化和准备bean的时候发生异常
l Object getBean(String, Class):返回一个以给定名字注册的bean。返回的bean将会被造型成给定的Class。如果bean不能被转型,相应的异常将会被抛出(BeanNotOfRequiredTypeException)。此外getBean(String)的所有规则也同样适用(见上面)
l boolean isSingleton(String):得到以给定名字注册的bean定义是一个singleton还是一个prototype。如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
l String[] getAliases(String):如果给定的bean名字在bean定义中有别名,则返回这些别名
3.6.1.得到一个FactoryBean而不是它的产品
有些时候我们有必要向BeanFactory请求一个实际的FactoryBean实例本身,而不是它生产出来的bean。在调用BeanFactory(包括ApplicationContext)的getBean方法的时候,在传入的参数bean id前面加一个“&”符号,就可以做到这一点。所以,对于一个给定的id为myBean的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的时候,bean post-processor必须通过类似下面的代码来手动地显示地注册:
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 factory
一个bean factory post-processor是一个实现了org.springframework.beans.factory.config. BeanFactoryPostProcessor接口的类。它将会被手动执行(BeanFactory的时候)或自动执行(ApplicationContext的时候),在BeanFactory构造出来后,对整个BeanFactory做某些类别的改变。Spring包含很多已存在的bean factory post-processor,比如PopertyResourceConfigure和PropertyPlaceHolderConfigurer(这两个将在下面介绍),以及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.PopertyPlaceholderConfigurer
PorpertyPlaceholderConfigurer作为一个bean factory post-processor实现,它可以用来将BeanFactory定义中的属性值放置到另一个单独的Java Properties格式的文件中。这将使用户不用对BeanFactory的主XML定义文件进行复杂和危险的修改,就可以定制一些基本的属性(比如说数据库的urls,用户名和密码)。
考虑一个BeanFactory定义的片断,这里的DataSource以及placeholder值是如下定义的:
在下面这个例子中,一个datasource被定义,并且我们会在一个外部的Porperties文件中配置一些相关属性。在运行时,我们为BeanFactory提供一个PorpertyPlaceholderConfigurer,它将会替换掉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中检查。这个行为能够通过设置configurer的systemPropertiesMode属性来定制。这个属性有三个值,一个让configurer总是覆盖,一个让它从不覆盖,一个让它仅在properties文件中找不到的时候覆盖。请参考PropertiesPlaceholderConfigurer的JavaDoc获得更多信息。
3.8.2.PorpertyOverrideConfigurer
另一个bean factory post-processor:PropertyOverrideConfigurer类似于PropertyPlaceholderConfiguer,但是与后者相比,前者的定义对于bean属性可以有缺省值或者根本没有值。如果相对重要的Properties文件没有包含一个bean属性对应的条目,那么缺省的上下文定义将被使用。
注意:bean factory的定义并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻明显地知道override configurer是否被使用了。在有多个PorpertyOverrideConfigurer对用一个bean属性定义了不同的值的时候,最后一个将取胜(取决于覆盖的机制)。
Properties文件的配置行应该是如下的格式:
beanName.property=value
一个properties文件的例子将会是下面这样的:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
如果一个BeanFactory包含了一个叫做dataSource的bean,这个bean又有driver和url属性,那么这个实例文件在这个BeanFactory中就可用。
3.9.注册附加的定制PropertyEditor
当用字符串值设置bean的属性时,BeanFactory实质上使用了标准的JavaBeans 的PropertyEditor将这些String转换为属性的复杂类型。Spring预先注册了很多定制的PropertyEditor(比如,将一个字符串表示的classname转换成真正的Class对象)。另外,一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么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的支持类,ContextLoader能够自动启动一个ApplicationContext并把这个过程作为J2EE web应用的普通启动进程的一部分。当然,这种情况下还是可以编程式地创建一个ApplicationContext。
Context包的基础是ApplicationContext接口,位于org.springframework.context包中。它是由BeanFactory衍生而来,提供BeanFactory所有的功能。为了以一种更框架导向的方式工作,使用分层和分等级的context,context包还提供如下:
l MessageSource,提供对i18n信息的访问
l 对资源的访问,比如URL和文件
l 事件传播向实现了ApplicationListener接口的bean
l 装载多个(分级的)context,允许每一个专注于一个特定的层次,比如应用的web层
因为ApplicationContext包括了BeanFactory所有的功能,所以通常建议先于BeanFactory使用,除了很少的一些限定场合比如在一个Applet中,内存的消耗是决定性的,几个额外kb都会产生不同的结果。接下来的章节将叙述ApplicationContext在BeanFactory的基本能力上增建的功能。
3.11.ApplicationContext增加的功能
正如上面说明的那样,ApplicationContext有几个区别于BeanFactory的特性。让我们一个一个地观察它们。
3.11.1.使用MessageSource
ApplicationContext接口继承自MessageSource接口,所以提供了信息化(i18n或者国际化)功能。同NestingMessageSource一起使用,就能够处理分等级的信息,这些是Spring提供的处理信息的基本接口。让我们快速地浏览一下这里定义的方法:
l String getMessage(String code, Object[] args, String default, Locale loc):从MessageSource取得信息的基本方法。如果对于指定的区域没有找到信息,则使用默认的信息(参数default)。传进去的参数args是做为信息中那些要被替换的值,这个是通过Java标准类库的MessageFormat实现的。
l String getMessage(String code, Object[] args, Locale loc):本质上和上一个方法是一样的,但是有一点区别:没有制定默认值;如果信息找不到,就会抛出一个NoSuchMessageException。
l String getMessage(MessageSourceResolveable resolvable, Locale locale):上面两个方法使用的所有属性都是封装到一个叫做MessageSourceResolvable的类中,你可以通过这个方法直接使用它。
当ApplicationContext被加载的时候,它会自动查找在context中定义的MessageSource bean。这个bean必须叫做messageSource。如果找到了这样的一个bean,所有对上述方法的调用将会被委托给这个找到的message source。如果没有找到message source,ApplicationContext将会尝试查它的parent是否包含这个名字的bean。如果有,它将会把找到的bean作为MessageSource。如果它最终没有找到任何的信息源,一个空的StaticMessageSource将会被实例化,为了能够接受对上述方法的请求。
Spring目前提供了两个MessageSource的实现。它们是ResourceBundleMessageSource和StaticMessageSource。两个都实现了NestingMessageSource以便能够嵌套信息。StaticMessageSource除了提供编程式的方式向source增加message外,几乎不会被用到。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,分别叫做format, exceptions和windows。使用JDK的标准做法:通过ResourceBundle处理信息,任何处理信息的请求都会被处理。TODO:举一个例子
3.11.2.传播事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口来提供的。如果context中部署了一个实现了ApplicationListener接口的bean,每次一个ApplicationEvent发布到ApplicationContext时,那个bean就会被通知。实质上,这是标准的Observer设计模式。Spring提供了三个标准事件:
表3.4。内建的事件
事件 解 释
ContextRefreshedEvent 当ApplicationContext已经实例化或刷新后发布的事件。这里实例化意味着:所有的bean被装载,singleton被预实例化,以及ApplicationContext准备好可用
ContextClosedEvent 当使用ApplicationContext的close()方法结束context的时候发布的事件。这里结束意味着:singleton被销毁
RequestHandledEvent 一个与web相关的事件,告诉所有的bean一个HTTP请求已经被响应了(这个事件将会在一个请求结束后被发布)。注意,这个事件只能应用于使用了Spring DispatcherServlet的wen应用
同样也可以实现自定义的事件。通过调用ApplicationContext的publishEvent()方法,并且指定一个参数,这个参数是你自定义的event class的一个实例。我们来看一个例子。首先是ApplicationContext:
<bean id="emailer" class="example.EmailBean"> <property name="blackList"> <list> <value>black@list.org</value> <value>white@list.org</value> <value>john@doe.org</value> </list> </property> </bean> <bean id="blackListListener" class="example.BlackListNotifier"> <property name="notificationAddress"> <value>spam@list.org</value> </property> </bean> |
然后是实际的bean:
public class EmailBean implements ApplicationContextAware { /** the blacklist */ private List blackList; public void setBlackList(List blackList) { this.blackList = blackList; } public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent evt = new BlackListEvent(address, text); ctx.publishEvent(evt); return; } // send email } }
public class BlackListNotifier implement ApplicationListener {
/** notification address */ private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; }
public void onApplicationEvent(ApplicationEvent evt) { if (evt instanceof BlackListEvent) { // notify appropriate person } } } |
当然,这个特定的例子或许可以用更好的方式实现(或许使用AOP特性),但是它还是足够说明基本的事件机制。
3.11.3.在Spring中使用资源
很多应用程序都需要访问资源。资源可以包括文件,以及其他如web页面或NNTP newsfeeds的东西。Spring提供了一个清晰透明的方案,以一种协议无关的方式访问资源。ApplicationContext接口包含一个方法负责这项工作。(getResource(String))
Resource类定义了几个方法,这几个方法被所有的Resource实现所共享:
表3.5。资源功能
方法 解 释
getInputStream() 在资源上打开一个InputStream,并返回这个InputStream。
exists() 验证资源时候存在,如果不存在返回false。
isOpen() 如果这个资源不能打开多个流将会返回true。对于除了基于文件的资源之外的一些资源会返回false,这些资源不能同时被多个流读取。
getDescription() 返回资源的描述,通常是全限定名或者实际的URL。
Spring提供了几个Resource的实现。它们都需要一个String表示的实际的资源位置。依据这个String,Spring将会自动为你选择正确的Resource实现。当向ApplicationContext请求一个资源时,Spring首先检查你指定的资源位置,寻找任何前缀。取决于ApplicationContext的实现,不同的Resource将会是可用的。Resource最好是使用ResourceEditor来配置,比如XmlBeanFactory。
3.12.ApplicationContext中的用户行为
BeanFactory已经提供了许多机制用来控制部署在其中的bean的生命周期(比如标志接口InitializingBean或DisposableBean,它们的结构和XmlBeanFactory配置中的init-method和destroy-method属性以及bean post-processor是相同的。在ApplicationContext中,这些也同样可以工作,但同时也增加了一些用于定制beans和容器行为的机制。
3.12.1.ApplicationContextAware标志接口
所有BeanFactory中可用的标志接口这里也可以使用。ApplicationContext又增加了一个bean可以实现的标志接口:org.springframework.context.ApplicationContextAware。如果一个bean实现了这个接口并且被部署到了context中,当这个bean创建的时候,将使用这个接口的setApplicationContext()方法回调这个bean,为这个bean提供一个指向当前context的引用,这个引用将被存储起来以便bean后面与context发生交互。
3.12.2.BeanPostProcessor
Bean post-processor(实现了org.springframework.beans.factory.config. BeanPostProcessor的java类)在上面已经叙述过了。这里还是值得再次提到,post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一个ApplicationContext中,一个实现了上述接口的部署的bean将会被自动查找并且作为一个bean post-processor被注册,在factory中的每一个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一样部署的时候context会自动识别和应用任何的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.注册额外的定制的PropertyEditor
正如前面曾提到的那样,Spring使用标准的JavaBeans PropertyEditor将字符串形式表示的属性值转换为属性真实的复杂的类型。CustomEditorConfigurer(一个特殊的bean factory post-processor)可以使ApplicationContext方便地增加对额外的PropertyEditor的支持。考虑一个用户类ExoticType,以及另外一个需要ExoticType作为属性的DependsOnExotic的类:
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上,然后就可以在需要的时候使用这个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,可以返回一个静态或非静态方法的调用结果。
下面是一个bean定义的例子,这个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方式进行操作。经由容器的第一次让factory生产对象的请求将会导致相应的那个工厂方法的调用,这个方法的返回值将会被缓存并且返回给当次请求和后来的请求。这个factory内部的singleton属性可以被设置为false,从而导致每次对象的请求都会调用那个目标方法。
通过给targetMethod属性设置一个静态方法名字的字符串来指定静态目标方法,而targetClass则用来指定定义静态方法的类。同样地,也可以指定一个目标实例方法,通过targetObject属性设置目标对象,targetMethod属性设置要在目标对象上调用的方法的名字。方法调用的参数可以通过设置args属性来指定。
3.15.在一个web应用中创建一个ApplicationContext
与BeanFactory总是被编程式地创建相比,ApplicationContext可以通过使用诸如ContextLoader而声明式地被创建。当然你也可以用ApplicationContext的任一种实现来编程式地创建它。首先,我们来看看ContextLoader以及它的实现。
ContextLoader有两个实现:ContextLoaderListener和ContextLoaderServlet。它们两个有着同样的功能,区别是listener不能在兼容Servlet2.2的容器中使用。自从Servelt2.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> --> |
在Spring中,两个最基本最重要的包是:org.springframework.beans和org.springframework.context包。这两个包中的代码为Spring的反向控制特性(也叫作依赖注射)提供了基础。BeanFactory[http://www.springframework.org/docs/api/org/ ;springframework/beans/factory/BeanFactory.html]提供了能够管理任何种类beans(对象)的先进的配置机制,潜在地利用任何一种存储设备。ApplicationContext建立在BeanFactory之上并增加了其他的功能,比如同Spring AOP特性更容易整合,信息资源处理(用于国际化),事件传播,声明式机制用于创建ApplicationContext和可选的父上下文以及与应用层相关的上下文(比如WebApplicationContext),以及其他的增强。
简而言之,BeanFactory提供了配置框架和基本的功能,而ApplicationContext为它增加了更强的功能,这些功能中的一些或许更加J2EE和企业中心(enterprise-centric)。一般来说,ApplicationContext是BeanFactory的完全超集,任何BeanFactory功能和行为的描述也同样被认为适用于ApplicationContext。
用户有时在特定的场合下不确定BeanFactory和ApplicationContext哪一个更适于使用。通常大部分在J2EE环境中构建的应用最好的选择是使用ApplicationContext,因为它不仅提供了BeanFactory所有的特性以及它自己附加的特性,而且还提供更声明化的方法去使用一些功能,这通常是令人满意的。你最好选择BeanFactory的主要场景通常是当内存使用是最主要的关注(比如在一个每kb都要计算的applet中),而且你也不需要ApplicationContext所有特性的时候。
这一章粗略地分为两部分,第一部分包括同时适用于BeanFactory和ApplicationContext的基本原则。第二部分会包括仅仅适用于ApplicationContext的一些特性。
3.2.BeanFactory和Bean定义(基础)
3.2.1.BeanFactory
BeanFactory是实际上实例化,配置和管理许多beans的容器。这些beans通常互相之间合作,因而也在它们之间产生依赖。这些依赖反映在被BeanFactory使用的配置数据中(一些依赖可能不像配置数据一样可见,而更可能在运行期作为bean之间程序交互的函数)。
一个BeanFactory用接口org.springframework.beans.factory.BeanFactory表示,这个接口有多个实现。最常使用的的简单的BeanFactory实现是org.springframework.beans.factory.xml.XmlBeanFactory。(这带有如下的暗示:ApplicationContext是BeanFactory的子类,所以大多数的用户更喜欢使用ApplicationContext的XML形式)。
尽管对于大多数场景,几乎所有的被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层提供支持代码,用来自动读取一个Spring ApplicationContext并把读取过程作为一个J2EE web应用启动过程的一部分。这个声明式的过程在这里描述:
编程式地操纵BeanFactory将会在后面接受,下面部分将集中描述BeanFactory的配置。
在最基础的级别上,一个BeanFactory配置由一个或多个BeanFactory必须管理的Bean的定义组成。在一个XmlBeanFactory中,在顶级的<beans>元素中配置一个或多个<bean>元素。
3.2.2.Bean定义
一个XmlBeanFactory实体中的Bean定义包括如下的细节以及其他一些信息:
l 一个classname:通常这是Bean定义中描述的那个bean的真正的实现类。然而如果一个bean使用一个静态工厂方法所创建而不是被普通的构造函数创建,那么这里实际上就是工厂类的classname。
l Bean行为配置元素:声明这个bean在容器的行为方式(比如prototype或singleton,自动装配模式,依赖检查模式,初始化和析构方法)。
l 构造函数参数和需要赋予新创建bean的属性:举一个例子,一个管理连接池的bean使用的连接数目(即可以指定为一个property也可以作为一个构造函数参数),或者池大小的限制数目。
l 这个bean需要起作用的其他beans:比如它的合作者(同业也可以作为属性或者构造函数的参数)。这个也被叫做依赖。
上面列出的概念直接转化为组成bean定义的一组元素。这些元素在下面的表格中列出,关于它们每一个的更详细的说明都有一个链接相对应。
表3.1。Bean定义说明
特性 更多信息
class 3.2.3节,bean class
id和name 3.2.4节,bean标志符(id和name)
singleton或prototype 3.2.5节,使用singleton还是不使用呢
构造函数参数 3.3.1节,设置bean的属性和合作者
bean属性 3.3.1节,设置bean的属性和合作者
自动装配模式 3.3.5节,自动装配合作者
依赖检查模式 3.3.6节,检查依赖
初始化方法 3.4.1节,生命周期接口
析构方法 3.4.1节,生命周期接口
注意bean定义是由真实的接口org.springframework.beans.factory.config.BeanDefinition以及它的各种子接口和实现所表示的。然而,绝大多数的用户代码不需要与BeanDefination直接作用。
3.2.3.bean class
class属性通常是强制性的(参看3.2.3.3节-通过实例工厂方法创建bean和3.5节-child bean的定义这两个例外情况),被用来达到两个目的中的一个。在绝大多数普通的场景中BeanFactory自己直接调用bean的构造函数来创建bean(相当于调用new的Java代码),class属性指定了需要创建的bean的类。在比较少的普通场景中BeanFactory调用某个类的静态的所谓的工厂方法来创建bean,class属性指定了实际包含静态工厂方法的那个类。(至于静态工厂方法返回的bean的类型是同一个类还是完全不同的另一个类,这并不重要)。
3.2.3.1.通过构造方法创建bean
当使用构造函数方式创建bean的时候,所有普通的类都可以被Spring使用以及同Spring兼容。这就是说,被创建的类不需要实现任何指定的接口或者按照特定的样式进行编码。仅仅指定bean的类就足够了。然而,由于依赖你给特定bean使用的IoC类型,你可能需要一个默认的(空的)构造函数。
另外,BeanFactory并不局限于管理真正的JavaBeans,事实上它也能够管理任何你想让它帮你管理的类。虽然很多使用Spring的人喜欢在BeanFactory中用真正的JavaBeans(仅包含一个默认的(无参数的)构造方法以及在属性后面包含适当的相对应的setter和getter),但是在你的BeanFactory中也可以使用特殊的非bean样式的类。举例来说,如果你需要使用一个遗留下来的完全没有遵守JavaBean规范的连接池,不要担心,Spring同样能够管理它。
使用XmlBeanFactory你可以像如下这样制定你的bean class:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/> |
至于为构造方法提供参数(可选的),以及对象实例构造后设置实例的属性,将会在后面叙述。
3.2.2.2.通过静态工厂方法创建Bean
当你定义一个使用静态工厂方法创建的bean,同时使用class属性指定包含静态工厂方法的类,这个时候就需要另一个叫做factory-method的属性来指定工厂方法的名字。Spring期待能够调用这个方法(包含可选的一组参数将在后面叙述)并且返回一个有效的对象,之后这个对象就会如同用构造方法创建的对象一样被看待。用户可以使用这样的bean定义在遗留代码中调用静态工厂。
下面是一个bean定义的例子,声明了一个bean要通过调用一个工厂方法被创建。要注意定义并没有指定返回对象的类型,只指定包含工厂方法的类。在这个例子中,createInstance必须是静态方法。
<bean id="exampleBean" class="examples.ExampleBean2" factory-method="createInstance"/> |
至于为工厂方法提供参数(可选的),以及对象实例被工厂方法返回后设置该实例的属性,将会在后面叙述。
3.2.3.3.通过实例工厂方法创建bean
与使用静态工厂方法创建bean非常类似的是,使用一个实例工厂方法(非静态的),通过调用一个已存在bean(这个bean应该是工厂类型)的工厂方法来创建新的bean。
要使用这种机制,class属性必须保留为空的,而且factory-bean属性必须指定一个包含工厂方法的bean的name(¥¥)。而工厂方法本身仍然要通过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属性的机制将在后面讨论,这个方案有一个暗示就是factory bean本身能够被容器通过依赖注射来管理和配置。
3.2.4.bean的标志符(id和name)
每一个bean都有一个或多个ids(也叫作标志符,或names;这些名词说的是一回事)。这些id在bean被管理的BeanFactory或ApplicationContext中必须是唯一的。一个bean差不多总是只有一个id,但是如果一个bean有超过一个的id,那么另外的那些本质上可以认为是别名。在一个XmlBeanFactory中(包括ApplicationContext的各种形式),你可以用id或者name属性来指定bean的id(s),并且在这两个或其中一个属性中至少指定一个id。id属性允许你指定一个id,并且它在XML DTD(定义文档)中作为一个真正的XML元素的ID属性被标记,所以parser能够在其他元素指回向它的时候做一些额外的校验。正因如此,用id属性指定bean的id是一个更好的方式。然而,XML规范严格限定了在XML ID中合法的字符。通常这并不是一个实在的约束,但是如果你有必要使用这些字符(在ID中的非法字符)中的一个,或者你想给bean引进其他的别名,那么你可以通过name属性指定一个或多个ids(用逗号“,”或者分号“;”分开)。
3.2.5.使用singleton还是不使用呢
Beans被定义为两种部署模式中的一种:singleton或non-singleton。(后一种也别叫作prototype,尽管这个名词用的不精确因为它并不是非常适合)。如果一个bean是singleton的,那么仅有一个共享的这个bean的实例被管理,所有id或者ids与这个bean定义匹配的beans请求都会导致那个唯一特定的bean实例被返回。
而non-singleton,prototype模式的bean部署产生的后果是:对这个bean的每次请求都会创建一个新的bean实例。这对于例如每个user需要一个独立的user对象这样的情况是非常理想的。
Beans默认被部署为singleton模式,除非你指定为另外的。要紧记把类型变为non-singletion(prototype)后,每一次对这个bean的请求都会导致一个新创建的bean而且这可能并不是你真正想要的。所以仅仅在绝对需要的时候才把模式改成prototype的。
在下面这个例子中,两个bean一个被定义为singleton的而另一个被定义为non-singleton(prototype)的。exampleBean会在客户端每次向BeanFactory请求它的时候被创建,而yetAnotherExample仅仅被创建一次;在对它每次请求的时候这个实例的引用会被返回。
<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之后,真正地注射这些依赖。This is fundamentally the inverse (hence the name Inversion of Control) of the bean instantiating or locating its dependencies on its own using direct construction of classes,就像ServiceLocator模式一样。我们不会详细阐述依赖注射的优点,很显然通过使用它:代码变得非常清晰;当beans不再自己查找他们的依赖而是由容器提供,甚至不需要知道在何处定位依赖以及依赖实际上是什么类型,这时高层次的解耦也变得很容易了。
正如上面提到的那样,反向控制/依赖注射存在两种主要的形式:
l 基于setter的依赖注射,是在调用无参的构造函数或无参的静态工厂方法实例化你的bean之后,通过在你的bean上调用setter实现的。在BeanFactory中定义的使用基于setter的注射依赖的bean是真正的JavaBeans。Spring一般提倡使用基于setter的依赖注射,因为很多的构造函数参数将会是笨重的,尤其在有些属性是可选的情况下。
l 基于构造函数的注射依赖,它是通过调用带有许多参数的构造方法实现的,每个参数表示一个合作者或者属性。另外,调用带有特定参数静态工厂方法来构造bean可以被认为差不多等同的,接下来的文字会把构造函数的参数和静态工厂方法的参数看作一样的。虽然Spring一般提倡在大多数情况下使用基于setter的依赖注射,但是Spring还是完全支持基于构造函数的依赖注射,因为你可能想要把它用在那些只提供多参数构造函数并且没有setter的遗留的beans上。另外对于一些比较简单的beans,一些人更喜欢使用构造函数方法以确保beans不会被以错误的形式构造。
BeanFactory同时支持这两种将依赖注射到被管理bean中的方式。(实际上它还支持在一些依赖已经通过构造函数方法注射后再使用基于setter的依赖注射)。依赖的配置是以BeanDefinition的形式出现,它和JavaBeans的PropertyEditors一起使用来了解如何把properties从一个格式转变为另一个。真正传送的值被变为Propertyvalue对象的形式。然而,大多数Spring的使用者并不要直接(比如编程式地)处理这些classes,而更多地使用一个XML定义文件,这个文件会在内部被转变为这些classes的实例,用来读取整个BeanFactory或ApplicationContext。
Bean依赖的决定通常按照下面所示发生:
l BeanFactory通过使用一个描述所有beans的配置被创建和实例化。大多数的Spring用户使用一个支持XML格式配置文件的BeanFactory或ApplicationContext变体。
l 每一个bean的依赖都是属性的形式,构造函数参数的形式,或者当用静态工厂方法代替普通构造函数时工厂方法的参数。这些依赖将会在bean真正被创建出来后提供给bean。
l 每一个属性或者构造函数参数要么是一个要被设置的值的真实定义,要么是一个指向BeanFactory中其他bean的引用。在ApplicationContext的情况下,这个引用可以指向一个父亲ApplicationContext中bean。
l 每一个是真实值的属性或构造函数参数,必须能够从(配置文件中)被指定的格式转变为属性或构造函数参数的真实类型。默认的Spring能够把一个字符串格式的值转变为所有内建的类型,比如int, long, String, boolean等等。另外当说到基于XML的BeanFactory变体的时候(包括ApplicationContext变体),它们已经为定义Lists, Maps, Sets和Properties集合类型提供了内在的支持。另外,Spring通过使用JavaBeans的PropertyEditor定义从而能够将字符串值转变为其他任意的类型。(你可以为BeanFactory提供你自己的PropertyEditor定义从而能够转变你自定义的类型;更多关于PropertyEditors的信息以及如何手动增加自定义的PropertyEditors请参看3.9节,注册附加的自定义PropertyEditors)。当一个bean属性是一个Java Class类型,Spring允许你用这个class的名字的字符串作为这个属性的值,ClassEditor这个内建的PropertyEditor会帮你把class的名字转变成真实的Class实例。
l 很重要的一点就是要了解:Srping在BeanFactory创建的时候要校验BeanFactory中每一个Bean的配置。这些校验包括作为Bean引用的属性必须实际引用一个合法的beans(比如被引用的bean也定义在BeanFactory中,或者当ApplicationContext时,在父亲上下文中)。但是,bean属性本身直到bean被真实建立的时候才被设置。对于那些是singleton并且被设置为pre-instantiated的bean来说(比如一个ApplicationContext中的singleton bean),Bean创建发生在BeanFactory被创建的时候,但是对于其他情况,发生在bean被请求的时候。当一个bean必须被创建时,它会潜在地导致一系列的其他bean被创建,像它的依赖以及它的依赖的依赖(如此下去)被创建和赋值。
l 通常你可以信任Spring做了正确的事情。它会在BeanFactory装载的时候检查处错误,包括对不存在bean的引用和循环引用。它会尽可能晚地真正设置属性和解决依赖(比如创建那些需要的依赖),在bean真正被创建的时候。这就意味着:就算一个BeanFactory被正确地装载,稍后当你请求一个bean的时候,如果创建那个bean或者它的依赖的时候出现了错误,这个BeanFactory也会生成一个异常。比如,如果一个bean抛出一个异常作为缺少或非法属性的结果,这样的情况就会发生。这种潜在地推迟一些配置错误可见性的行为正是ApplicationContext默认预实例化singleton beans的原因。以前期的时间和内存为代价在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的构造函数参数被传入。现在考虑一下替换掉构造函数的变体,Spring被告知调用一个静态工厂方法来返回一个对象的实例:
<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元素被支持,构造函数事实上也是一样的被使用。这些参数是可选的。很重要的一点是工厂方法所返回的class类型不一定要和包含着个静态工厂方法的class一致,虽然上面这个例子中是一样的。前面所提到的实例工厂方法(non-static)用法基本上是一样的(除了使用factory-bean属性代替class属性),在这里就不再详细叙述了。
3.3.2.深入Bean属性和构造函数参数
正如前面提到的那样,bean的属性和构造函数参数可以被定义为其他managed beans的引用(合作者),或者内联定义的值。为了达到这个目的,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的id 或name的字符串来设置属性。
<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属性的class仅仅会在这个class被Spring实例化的时候做它自己的验证,很可能在容器真正部署完很久之后。
另外,如果被引用的bean在同一个xml文件中而且bean的名称是bean的id,那么local属性就可以被使用。它会让XML parser自己更早地验证bean的名称,在XML文档解析的时候。
<property name="targetName"> <idref local="theTargetBean"/> </property> |
ref元素是最后一个能在property元素中使用的元素。它是用来给一些特定的属性设值的,这些属性是容器管理的其他bean的引用(可以叫做合作者)。正如前一节提到的,被引用的bean被认为是那些属性要被设置的bean的依赖,被应用的bean将会在属性设置之前需要时(如果是一个singleton bean可能已经被容器初始化),一经请求就被初始化。所有的引用根本上就只是一个指向其他对象的引用,不过这里其它对象的id/name如何被指定有3种形式,这3种不同形式决定scoping和验证如何被处理。
用ref标签的bean属性指定目标bean是最常见的形式,对于同一个BeanFactory/ ApplicationContext(无论是否在同一个XML文件中)中,或者parent BeanFactory/ ApplicationContext中的任何bean,允许创建指向它的bean。bean属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中任何一个值相同。
<ref bean="someBean"/>
用local属性指定目标bean可以利用XML parser的能力在同一个文件中验证XML id引用。local属性的值必须与目标bean的id属性一致。如果在同一个文件中没有匹配的元素,XML parser将会产生一个错误。因此,如果目标bean在同一个XML文件中,那么使用local形式将是最好的选择(为了能够尽可能早的发现错误)。
<ref local="someBean"/>
用parent属性指定目标bean允许一个引用指向当前BeanFactory(ApplicationContext)的parent BeanFactory(ApplicationContext)中的bean。Parent属性的值可以同目标bean的id属性相同,也可以同目标bean的name属性中的一个值相同,而且目标bean必须在当前BeanFactory(ApplicationContext)的parent中。当需要用某些proxy包装一个parent上下文中存在的bean(可能和parent中的有同样的name),所以需要原始的对象用来包装它。
<ref parent="someBean"/>
3.3.3.方法注射
对于大部分的用户来说,容器中多数的beans将会是singletons。当一个singleton bean需要同另一个singleton bean合作(使用),或者一个non-singleton bean需要同另一个non-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库在客户端的class之上修改二进制码,从而实现上述的场景要求。
在客户端的class包含需要注入的方法,方法定义必须是如下形式的抽象(具体)定义:
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(不管是明确指出的还是默认的)而部署,同一个singleShotHelper实例将会每次被返回。
Lookup方法注射能够同构造函数注射结合(对创建的bean提供可选的构造函数参数),也可以同setter注射结合(在创建的bean之上设置属性)。
3.3.3.2.任意方法的替换
没有lookup方法注射那么有用的另一种方法注射的形式是,用另外的方法实现替换掉被管理bean的任意的方法。用户可以安全地跳过这一节(叙述这个有些高级的特性),除非这个功能确实需要。
在一个XmlBeanFactory中,对于一个被部署的bean,replaced-method元素可以用来把已存在的方法实现替换为其他的实现。考虑如下的class,有一个我们想要重写的computevalue方法:
public class MyvalueCalculator { public String computevalue(String input) { ... some real code } ... some other methods } |
需要为新方法定义提供实现org.springframework.beans.factory.support.MethodReplacer接口的类。
[code]/** 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 ...;
}[/code]
部署原始class和指定方法重写的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元素来完成的。与这种方式不同的是,有时一个知道container的bean仅仅会被给与它的依赖的id(使用一个字符串值或等价的idref元素)。接着第一个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不自动装配。通过使用自动装配,减少(或消除)指定属性(或构造函数参数)的需要,节省显著的键入工作是有可能的。在XmlBeanFactory中,使用bean元素的autowire属性来指定bean定义的自动装配模式。以下是允许的值:
表3.2。自动装配模式
模式 解 释
no 不使用自动装配。Bean的引用必须通过ref元素定义。这是默认的配置,在较大的部署环境中不鼓励改变这个配置,因为明确的指定合作者能够得到更多的控制和清晰性。从某种程度上说,这也是系统结构的文档形式。
byName 通过属性名字进行自动装配。这个选项会会检查BeanFactory,查找一个与将要装配的属性同样名字的bean 。比如,你有一个bean的定义被设置为通过名字自动装配,它包含一个master属性(也就是说,它有一个setMaster()方法),Spring就会查找一个叫做master的bean定义,然后用它来设置master属性。
byType 如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个的情况,则一个致命的异常会被抛出,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check=”objects”属性的值来指定在这种情况下应该抛出错误。
constructor 这个同byType类似,不过是应用于构造函数参数的。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。
autodetect 通过对bean class的内省来选择constructor或byType。如果找到一个缺省的构造函数,那么就会应用byType。
注意:显式的依赖,比如property和construtor-arg元素,总会覆盖自动装配。自动装配的行为可以和依赖检查结合使用,依赖检查会在自动装配完成后发生。
注意:正如我们已经提到过的,对于大型的应用,自动装配不鼓励使用,因为它去除了你的合作类的透明性和结构。
3.3.6.检查依赖
对于要部署到BeanFactory的bean的未解决的依赖,Spring有能力去检查它们的存在性。这些依赖要么是bean的JavaBean式的属性,在bean的定义中并没有个它们设置真实的值,要么是通过自动装配特性被提供。
当你想确保所有的属性(或者某一特定类型的所有属性)都被设置到bean上面的时候,这项特性就很有用了。当然,在很多情况下一个bean class的很多属性都会有缺省的值,或者一些属性并不能应用于所有的应用场景,那么这个特性的作用就有限了 。依赖检查能够分别对每一个bean应用或取消应用,就像自动装配功能一样。缺省的是不检查依赖。依赖检查可以以几种不同的模式处理。在XmlBeanFactory中,这个通过bean定义中的dependency-check属性来指定,这个属性有以下的值:
表3.3。依赖检查模式
模式 解 释
none 不进行依赖检查。没有指定值的bean属性简单地不进行设值。
simple 对基本类型和集合(除了合作者外[比如其他的bean]的所有东西)进行依赖检查。
object 对合作者进行依赖检查。
all 对合作者,基本类型和集合都进行依赖检查。
3.4.自定义bean的自然特性3.4.1.生命周期接口
Spring提供了一些标志接口,用来改变BeanFactory中的bean的行为。它们包括InitailizingBean和DisposableBean。实现这些接口将会导致BeanFactory为调用前一个的afterPropertiesSet(),调用后一个destroy(),从而使得bean可以在初始化和析构后做一些特定的动作。
在内部,Spring使用BeanPostProcessors来处理它能找到的标志接口以及调用适当的方法。如果你需要自定义的特性或者其他的Spring没有提供的生命周期行为,你可以实现自己的BeanPostProcessor。关于这方面更多的内容可以看这里:3.7节,用BeanPostProcessors定制bean。
所有的生命周期的标志接口都在下面叙述。在附录的一节中,你可以找到相应的图,展示了Spring如何管理beans;那些生命周期的特性如何改变你的bean的自然特性以及它们如何被管理。
3.4.1.1.InitializingBean/初始化方法
实现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 } } |
同下面的完全一样,但却不把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } } |
3.4.1.2.DisposableBean/析构方法
实现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="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like closing connection) } } |
和下面的定义是一样的,当时去不会把代码耦合于Spring:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work } } |
重要的提示:当以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;
}
这允许beans可以编程式地操控创建它们的BeanFactory,既可以直接使用org.springframework.beans.factory.BeanFactory接口,也可以将引用cast为已知的子类型从而获得更多的功能。这个特性主要用于编程式地取得其他beans。虽然在一些场景下这个功能是有用的,但是一般来说它应该避免使用,因为它使代码与Spring耦合在一起,而且也不遵循反向控制的风格(合作者被当作属性提供给beans)。
3.4.2.2.BeanNameAware
如果一个bean实现了org.springframework.beans.factory.BeanNameAware接口,并且被部署到一个BeanFactory中,那么BeanFactory就会通过这个接口来调用bean来通知这个bean它(bean)被部署的id。这个回调发生在普通的bean属性设置之后,初始化回调比如InitializiongBean的afterPropertiesSet方法(或者自定义的init-method)调用之前。
3.4.3.FactoryBean
接口org.springframework.beans.factory.FactoryBean将会被自己是factory的对象所实现。BeanFactory接口提供了三个方法:
l Ojbect getObject():必须返回一个这个factory创建的对象的实例。这个实例可以使共享的(取决于这个工厂返回的是singleton还是prototype)。
l boolean isSingleton():必须返回true如果Factory返回singletons,否则返回false。
l Class getObjectType():返回getObject()方法返回的对象的类型,或者如果类型不是预先知道的则返回null。
3.5.child bean定义
一个bean定义可能会包含大量的配置信息,包括容器相关的信息(比如初始化方法,静态工厂方法等等)以及构造函数参数和属性的值。一个child bean定义是一个能够从parent bean定义继承配置数据的bean定义。然后它可以覆盖一些值,或者添加一些其他需要的。使用父和子的bean定义可以节省很多的输入工作。实际上,这就是一种模版形式。当编程式地使用一个BeanFactory,child bean定义用ChildBeanDefinitiaon类表示。大多数的用户从来不需要在这个级别上使用它们,取而代之的是,在诸如XmlBeanFactory中声明式地配置bean定义。在一个XmlBeanFactory的bean定义中,使用parent属性指出一个child bean定义,而parent 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> |
如果child bean定义没有指定class,一个child bean定义将使用父定义的class,当然也可以覆盖它。在后面一种情况中,child bean的class必须同parent bean的兼容,比如它必须能够接受父亲的属性值。
一个child bean定义将会从父亲处继承构造函数参数,属性值以及方法,并且可以选择地增加新的值。如果初始化方法,销毁方法和/或静态工厂方法被指定了,它们就会覆盖父亲相应的设置。
剩余的设置将总是从子定义处得到:depends on, autowire mode, dependency check, singleton, lazy init。
在下面的例子中父定义并没有指定一个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> |
这个parent bean就无法自己实例化;它实际上仅仅是一个纯模版或抽象bean,仅仅充当子定义的父定义。若要尝试单独使用这样的parent bean(比如将它作为其他bean的ref属性而引用,或者直接使用这个parent bean的id调用getBean()方法),将会导致一个错误。同样地,容器内部的preInstantiateSingletons方法会完全忽略这种既没有parent也没有class属性的bean定义,因为它们是不完整的。
重要的注意点:这里并没有办法显式地声明一个bean定义为抽象的。如果一个bean确实有一个class定义,那么它就能够被实例化。而且要注意XmlBeanFactory默认地将会预实例化所有的singletons。因此很重要的一点是:如果你有一个(父)bean定义指定了class属性,而你又想仅仅把它当作模板使用,那么你必须保证将lazy-init属性设置为true(或者将bean标记为non-singleton),否则XmlBeanFactory(和其他可能的容器)将会预实例化它。
3.6.同BeanFactory交互BeanFactory本质上不过是高级factory的借口,有能力维护不同beans和它们依赖的注册。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个方法:
l boolean containsBean(String):如果BeanFactory包含一个与所给名称匹配的bean定义则返回true
l Object getBean(String):返回一个以所给名字注册的bean的实例。返回一个singleton的共享的实例还是一个新创建的实例,这取决于bean在BeanFactory配置中如何被配置的。一个BeanException将会在下面两种情况中抛出:bean没有被找到(在这种情况下,抛出的是NoSuchBeanDefinitionException),或者在实例化和准备bean的时候发生异常
l Object getBean(String, Class):返回一个以给定名字注册的bean。返回的bean将会被造型成给定的Class。如果bean不能被转型,相应的异常将会被抛出(BeanNotOfRequiredTypeException)。此外getBean(String)的所有规则也同样适用(见上面)
l boolean isSingleton(String):得到以给定名字注册的bean定义是一个singleton还是一个prototype。如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
l String[] getAliases(String):如果给定的bean名字在bean定义中有别名,则返回这些别名
3.6.1.得到一个FactoryBean而不是它的产品
有些时候我们有必要向BeanFactory请求一个实际的FactoryBean实例本身,而不是它生产出来的bean。在调用BeanFactory(包括ApplicationContext)的getBean方法的时候,在传入的参数bean id前面加一个“&”符号,就可以做到这一点。所以,对于一个给定的id为myBean的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的时候,bean post-processor必须通过类似下面的代码来手动地显示地注册:
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 factory
一个bean factory post-processor是一个实现了org.springframework.beans.factory.config. BeanFactoryPostProcessor接口的类。它将会被手动执行(BeanFactory的时候)或自动执行(ApplicationContext的时候),在BeanFactory构造出来后,对整个BeanFactory做某些类别的改变。Spring包含很多已存在的bean factory post-processor,比如PopertyResourceConfigure和PropertyPlaceHolderConfigurer(这两个将在下面介绍),以及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.PopertyPlaceholderConfigurer
PorpertyPlaceholderConfigurer作为一个bean factory post-processor实现,它可以用来将BeanFactory定义中的属性值放置到另一个单独的Java Properties格式的文件中。这将使用户不用对BeanFactory的主XML定义文件进行复杂和危险的修改,就可以定制一些基本的属性(比如说数据库的urls,用户名和密码)。
考虑一个BeanFactory定义的片断,这里的DataSource以及placeholder值是如下定义的:
在下面这个例子中,一个datasource被定义,并且我们会在一个外部的Porperties文件中配置一些相关属性。在运行时,我们为BeanFactory提供一个PorpertyPlaceholderConfigurer,它将会替换掉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中检查。这个行为能够通过设置configurer的systemPropertiesMode属性来定制。这个属性有三个值,一个让configurer总是覆盖,一个让它从不覆盖,一个让它仅在properties文件中找不到的时候覆盖。请参考PropertiesPlaceholderConfigurer的JavaDoc获得更多信息。
3.8.2.PorpertyOverrideConfigurer
另一个bean factory post-processor:PropertyOverrideConfigurer类似于PropertyPlaceholderConfiguer,但是与后者相比,前者的定义对于bean属性可以有缺省值或者根本没有值。如果相对重要的Properties文件没有包含一个bean属性对应的条目,那么缺省的上下文定义将被使用。
注意:bean factory的定义并不会意识到被覆盖,所以仅仅察看XML定义文件并不能立刻明显地知道override configurer是否被使用了。在有多个PorpertyOverrideConfigurer对用一个bean属性定义了不同的值的时候,最后一个将取胜(取决于覆盖的机制)。
Properties文件的配置行应该是如下的格式:
beanName.property=value
一个properties文件的例子将会是下面这样的:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
如果一个BeanFactory包含了一个叫做dataSource的bean,这个bean又有driver和url属性,那么这个实例文件在这个BeanFactory中就可用。
3.9.注册附加的定制PropertyEditor
当用字符串值设置bean的属性时,BeanFactory实质上使用了标准的JavaBeans 的PropertyEditor将这些String转换为属性的复杂类型。Spring预先注册了很多定制的PropertyEditor(比如,将一个字符串表示的classname转换成真正的Class对象)。另外,一个class的PropertyEditor如果被恰当地命名并且放在与它提供支持的class同一个包内,那么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的支持类,ContextLoader能够自动启动一个ApplicationContext并把这个过程作为J2EE web应用的普通启动进程的一部分。当然,这种情况下还是可以编程式地创建一个ApplicationContext。
Context包的基础是ApplicationContext接口,位于org.springframework.context包中。它是由BeanFactory衍生而来,提供BeanFactory所有的功能。为了以一种更框架导向的方式工作,使用分层和分等级的context,context包还提供如下:
l MessageSource,提供对i18n信息的访问
l 对资源的访问,比如URL和文件
l 事件传播向实现了ApplicationListener接口的bean
l 装载多个(分级的)context,允许每一个专注于一个特定的层次,比如应用的web层
因为ApplicationContext包括了BeanFactory所有的功能,所以通常建议先于BeanFactory使用,除了很少的一些限定场合比如在一个Applet中,内存的消耗是决定性的,几个额外kb都会产生不同的结果。接下来的章节将叙述ApplicationContext在BeanFactory的基本能力上增建的功能。
3.11.ApplicationContext增加的功能
正如上面说明的那样,ApplicationContext有几个区别于BeanFactory的特性。让我们一个一个地观察它们。
3.11.1.使用MessageSource
ApplicationContext接口继承自MessageSource接口,所以提供了信息化(i18n或者国际化)功能。同NestingMessageSource一起使用,就能够处理分等级的信息,这些是Spring提供的处理信息的基本接口。让我们快速地浏览一下这里定义的方法:
l String getMessage(String code, Object[] args, String default, Locale loc):从MessageSource取得信息的基本方法。如果对于指定的区域没有找到信息,则使用默认的信息(参数default)。传进去的参数args是做为信息中那些要被替换的值,这个是通过Java标准类库的MessageFormat实现的。
l String getMessage(String code, Object[] args, Locale loc):本质上和上一个方法是一样的,但是有一点区别:没有制定默认值;如果信息找不到,就会抛出一个NoSuchMessageException。
l String getMessage(MessageSourceResolveable resolvable, Locale locale):上面两个方法使用的所有属性都是封装到一个叫做MessageSourceResolvable的类中,你可以通过这个方法直接使用它。
当ApplicationContext被加载的时候,它会自动查找在context中定义的MessageSource bean。这个bean必须叫做messageSource。如果找到了这样的一个bean,所有对上述方法的调用将会被委托给这个找到的message source。如果没有找到message source,ApplicationContext将会尝试查它的parent是否包含这个名字的bean。如果有,它将会把找到的bean作为MessageSource。如果它最终没有找到任何的信息源,一个空的StaticMessageSource将会被实例化,为了能够接受对上述方法的请求。
Spring目前提供了两个MessageSource的实现。它们是ResourceBundleMessageSource和StaticMessageSource。两个都实现了NestingMessageSource以便能够嵌套信息。StaticMessageSource除了提供编程式的方式向source增加message外,几乎不会被用到。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,分别叫做format, exceptions和windows。使用JDK的标准做法:通过ResourceBundle处理信息,任何处理信息的请求都会被处理。TODO:举一个例子
3.11.2.传播事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口来提供的。如果context中部署了一个实现了ApplicationListener接口的bean,每次一个ApplicationEvent发布到ApplicationContext时,那个bean就会被通知。实质上,这是标准的Observer设计模式。Spring提供了三个标准事件:
表3.4。内建的事件
事件 解 释
ContextRefreshedEvent 当ApplicationContext已经实例化或刷新后发布的事件。这里实例化意味着:所有的bean被装载,singleton被预实例化,以及ApplicationContext准备好可用
ContextClosedEvent 当使用ApplicationContext的close()方法结束context的时候发布的事件。这里结束意味着:singleton被销毁
RequestHandledEvent 一个与web相关的事件,告诉所有的bean一个HTTP请求已经被响应了(这个事件将会在一个请求结束后被发布)。注意,这个事件只能应用于使用了Spring DispatcherServlet的wen应用
同样也可以实现自定义的事件。通过调用ApplicationContext的publishEvent()方法,并且指定一个参数,这个参数是你自定义的event class的一个实例。我们来看一个例子。首先是ApplicationContext:
<bean id="emailer" class="example.EmailBean"> <property name="blackList"> <list> <value>black@list.org</value> <value>white@list.org</value> <value>john@doe.org</value> </list> </property> </bean> <bean id="blackListListener" class="example.BlackListNotifier"> <property name="notificationAddress"> <value>spam@list.org</value> </property> </bean> |
然后是实际的bean:
public class EmailBean implements ApplicationContextAware { /** the blacklist */ private List blackList; public void setBlackList(List blackList) { this.blackList = blackList; } public void setApplicationContext(ApplicationContext ctx) { this.ctx = ctx; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent evt = new BlackListEvent(address, text); ctx.publishEvent(evt); return; } // send email } }
public class BlackListNotifier implement ApplicationListener {
/** notification address */ private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; }
public void onApplicationEvent(ApplicationEvent evt) { if (evt instanceof BlackListEvent) { // notify appropriate person } } } |
当然,这个特定的例子或许可以用更好的方式实现(或许使用AOP特性),但是它还是足够说明基本的事件机制。
3.11.3.在Spring中使用资源
很多应用程序都需要访问资源。资源可以包括文件,以及其他如web页面或NNTP newsfeeds的东西。Spring提供了一个清晰透明的方案,以一种协议无关的方式访问资源。ApplicationContext接口包含一个方法负责这项工作。(getResource(String))
Resource类定义了几个方法,这几个方法被所有的Resource实现所共享:
表3.5。资源功能
方法 解 释
getInputStream() 在资源上打开一个InputStream,并返回这个InputStream。
exists() 验证资源时候存在,如果不存在返回false。
isOpen() 如果这个资源不能打开多个流将会返回true。对于除了基于文件的资源之外的一些资源会返回false,这些资源不能同时被多个流读取。
getDescription() 返回资源的描述,通常是全限定名或者实际的URL。
Spring提供了几个Resource的实现。它们都需要一个String表示的实际的资源位置。依据这个String,Spring将会自动为你选择正确的Resource实现。当向ApplicationContext请求一个资源时,Spring首先检查你指定的资源位置,寻找任何前缀。取决于ApplicationContext的实现,不同的Resource将会是可用的。Resource最好是使用ResourceEditor来配置,比如XmlBeanFactory。
3.12.ApplicationContext中的用户行为
BeanFactory已经提供了许多机制用来控制部署在其中的bean的生命周期(比如标志接口InitializingBean或DisposableBean,它们的结构和XmlBeanFactory配置中的init-method和destroy-method属性以及bean post-processor是相同的。在ApplicationContext中,这些也同样可以工作,但同时也增加了一些用于定制beans和容器行为的机制。
3.12.1.ApplicationContextAware标志接口
所有BeanFactory中可用的标志接口这里也可以使用。ApplicationContext又增加了一个bean可以实现的标志接口:org.springframework.context.ApplicationContextAware。如果一个bean实现了这个接口并且被部署到了context中,当这个bean创建的时候,将使用这个接口的setApplicationContext()方法回调这个bean,为这个bean提供一个指向当前context的引用,这个引用将被存储起来以便bean后面与context发生交互。
3.12.2.BeanPostProcessor
Bean post-processor(实现了org.springframework.beans.factory.config. BeanPostProcessor的java类)在上面已经叙述过了。这里还是值得再次提到,post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一个ApplicationContext中,一个实现了上述接口的部署的bean将会被自动查找并且作为一个bean post-processor被注册,在factory中的每一个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一样部署的时候context会自动识别和应用任何的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.注册额外的定制的PropertyEditor
正如前面曾提到的那样,Spring使用标准的JavaBeans PropertyEditor将字符串形式表示的属性值转换为属性真实的复杂的类型。CustomEditorConfigurer(一个特殊的bean factory post-processor)可以使ApplicationContext方便地增加对额外的PropertyEditor的支持。考虑一个用户类ExoticType,以及另外一个需要ExoticType作为属性的DependsOnExotic的类:
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上,然后就可以在需要的时候使用这个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,可以返回一个静态或非静态方法的调用结果。
下面是一个bean定义的例子,这个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方式进行操作。经由容器的第一次让factory生产对象的请求将会导致相应的那个工厂方法的调用,这个方法的返回值将会被缓存并且返回给当次请求和后来的请求。这个factory内部的singleton属性可以被设置为false,从而导致每次对象的请求都会调用那个目标方法。
通过给targetMethod属性设置一个静态方法名字的字符串来指定静态目标方法,而targetClass则用来指定定义静态方法的类。同样地,也可以指定一个目标实例方法,通过targetObject属性设置目标对象,targetMethod属性设置要在目标对象上调用的方法的名字。方法调用的参数可以通过设置args属性来指定。
3.15.在一个web应用中创建一个ApplicationContext
与BeanFactory总是被编程式地创建相比,ApplicationContext可以通过使用诸如ContextLoader而声明式地被创建。当然你也可以用ApplicationContext的任一种实现来编程式地创建它。首先,我们来看看ContextLoader以及它的实现。
ContextLoader有两个实现:ContextLoaderListener和ContextLoaderServlet。它们两个有着同样的功能,区别是listener不能在兼容Servlet2.2的容器中使用。自从Servelt2.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。如果它存在,它就会将那个String用预先定义的分隔符(逗号,分号和空格)分开并将这些值作为application context将要搜索的位置。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方案代理到一个普通的java实现的对象,这个对象由BeanFactory产生。虽然理想地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),以及其它可能产生的副作用。作为替换,诸如ContextSingletonBeanFactoryLocator和SingletonBeanFactoryLocator的类可以被用来在需要的时候以有效的singleton方式加载多个分级的(比如一个是另一个的parent)BeanFactory或ApplicationContext,这些将会作为web-app ApplicationContext的parents。这样做的结果就是,底层的bean定义只在需要的时候加载并且只被加载一次。
3.16.1.使用SingletonBeanFactoryLocator和ContextSingletonBeanFactoryLocator
你可以查看它们各自的JavaDoc来获得详细的使用例子。正如在EJB那一章提到的,Spring为EJB提供的方便的基类一般使用一个non-singleton的BeanFactoryLocator实现,这个可以在需要的时候被上面两个类的使用所替换。