3.3.1.3. 一些例子
(1)首先是一个用XML格式定义的Setter DI例子。相关的XML配置如下:
通过 setter 方法注入:
<bean id="exampleBean" class="examples.ExampleBean"> <property name="beanOne"><ref bean="anotherExampleBean"/></property> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
</pre><pre class="programlisting" name="code" style="word-wrap: break-word;">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; } }
正如你所看到的,bean类中的setter方法与xml文件中配置的属性是一一对应的。
接着是构造器注入的例子。以下是xml配置代码以及相对应的java类代码。
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg><ref bean="anotherExampleBean"/></constructor-arg> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
<span style="font-family: Arial, Helvetica, sans-serif;">public class ExampleBean {</span>
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; } }
如你所见,在xml bean定义中指定的构造器参数将被用来作为传递给类ExampleBean构造器的参数。
现在来研究一个替代构造器的方法,采用静态工厂方法返回对象实例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { <span class="lineannotation" style="font-style: italic;">// a private constructor</span> 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 createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations ... return eb; } }
请注意,传给静态工厂方法的参数由constructor-arg元素提供,这与使用构造器注入时完全一样。而且,重要的是,工厂方法所返回的实例的类型并不一定要与包含static工厂方法的类类型一致。尽管在此例子中它的确是这样。非静态的实例工厂方法与此相同(除了使用factory-bean属性替代class属性外),因而不在此细述。
构造器参数将根据类型来进行匹配。如果bean定义中的构造器参数类型明确,那么bean定义中的参数顺序就是对应构造器参数的顺序。考虑以下的类...
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
这里的参数类型非常明确(当然前提是假定类Bar与 Baz在继承层次上并无任何关系)。因此下面的配置将会很好地工作,且无须显式地指定构造器参数索引及其类型。
<beans> <bean name="foo" class="x.y.Foo"> <constructor-arg> <bean class="x.y.Bar"/> </constructor-arg> <constructor-arg> <bean class="x.y.Baz"/> </constructor-arg> </bean> </beans>
当引用的bean类型已知,则匹配没有问题(如上述的例子)。但是当使用象<value>true<value>这样的简单类型时,Spring将无法决定该值的类型,因而仅仅根据类型是无法进行匹配的。考虑以下将在下面两节使用的类:
package examples; public class ExampleBean { <span class="lineannotation" style="font-style: italic;">// No. of years to the calculate the Ultimate Answer</span> private int years; <span class="lineannotation" style="font-style: italic;">// The Answer to Life, the Universe, and Everything</span> private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
针对上面的这种情况,我们可以在构造器参数定义中使用type属性来显式的指定参数所对应的简单类型。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
通过使用index属性可以显式的指定构造器参数出现顺序。例如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
使用index属性除了可以解决多个简单类型构造参数造成的模棱两可的问题之外,还可以用来解决两个构造参数类型相同造成的麻烦。注意:index属性值从0开始。
正如前面所提到的,bean的属性及构造器参数既可以引用容器中的其他bean,也可以是内联(inline,在spring的XML配置中使用<property/>和<constructor-arg/>元素定义)bean。
<value/>元素通过字符串来指定属性或构造器参数的值。正如前面所提到的,JavaBean PropertyEditor将用于把字符串从java.lang.String类型转化为实际的属性或参数类型。
<bean id="myDataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <span class="lineannotation" style="font-style: italic;"><!-- results in a <code class="methodname">setDriverClassName(String)</code> call --></span> <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>
idref元素用来将容器内其它bean的id传给<constructor-arg/> 或 <property/>元素,同时提供错误验证功能。
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
上述bean定义片段完全地等同于(在运行时)以下的片段:
<bean id="theTargetBean" class="..."/> <bean id="client" class="..."> <property name="targetName"> <value>theTargetBean</value> </property> </bean>
第一种形式比第二种更可取的主要原因是,使用idref标记允许容器在部署时 验证所被引用的bean是否存在。而第二种方式中,传给client bean的targetName属性值并没有被验证。任何的输入错误仅在client bean实际实例化时才会被发现(可能伴随着致命的错误)。如果client bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。
此外,如果被引用的bean在同一XML文件内,且bean名字就是bean id,那么可以使用local属性,此属性允许XML解析器在解析XML文件时来对引用的bean进行验证。
<property name="targetName"> <span class="lineannotation" style="font-style: italic;"><!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown --></span> <idref local="theTargetBean"/> </property>
上面的例子与在ProxyFactoryBean bean定义中使用<idref/>元素指定AOP interceptor的相同之处在于:如果使用<idref/>元素指定拦截器名字,可以避免因一时疏忽导致的拦截器ID拼写错误。
在<constructor-arg/>或<property/>元素内部还可以使用ref元素。该元素用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。尽管都是对另外一个对象的引用,但是通过id/name指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。
第一种形式也是最常见的形式是通过使用<ref/>标记指定bean属性的目标bean,通过该标签可以引用同一容器或父容器内的任何bean(无论是否在同一XML文件中)。XML 'bean'元素的值既可以是指定bean的id值也可以是其name值。
<ref bean="someBean"/>
第二种形式是使用ref的local属性指定目标bean,它可以利用XML解析器来验证所引用的bean是否存在同一文件中。local属性值必须是目标bean的id属性值。如果在同一配置文件中没有找到引用的bean,XML解析器将抛出一个例外。如果目标bean是在同一文件内,使用local方式就是最好的选择(为了尽早地发现错误)。
<ref local="someBean"/>
第三种方式是通过使用ref的parent属性来引用当前容器的父容器中的bean。parent属性值既可以是目标bean的id值,也可以是name属性值。而且目标bean必须在当前容器的父容器中。使用parent属性的主要用途是为了用某个与父容器中的bean同名的代理来包装父容器中的一个bean(例如,子上下文中的一个bean定义覆盖了他的父bean)。
<span class="lineannotation" style="font-style: italic;"><!-- in the parent context --></span> <bean id="accountService" class="com.foo.SimpleAccountService"> <span class="lineannotation" style="font-style: italic;"><!-- insert dependencies as required as here --></span> </bean>
<span class="lineannotation" style="font-style: italic;"><!-- in the child (descendant) context --></span> <bean id="accountService" <span class="lineannotation" style="font-style: italic;"><-- notice that the name of this bean is the <span class="bold"><strong>same</strong></span> as the name of the <code class="literal">'parent'</code> bean</span> class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref parent="accountService"/> <span class="lineannotation" style="font-style: italic;"><-- notice how we refer to the <span class="bold"><strong>parent</strong></span> bean</span> </property> <span class="lineannotation" style="font-style: italic;"><!-- insert other configuration and dependencies as required as here --></span> </bean>
('parent'属性的使用并不常见。)
所谓的内部bean(inner bean)是指在一个bean的<property/>或 <constructor-arg/>元素中使用<bean/>元素定义的bean。内部bean定义不需要有id或name属性,即使指定id 或 name属性值也将会被容器忽略。
以下是个关于内部bean例子。
<bean id="outer" class="..."> <span class="lineannotation" style="font-style: italic;"><!-- instead of using a reference to a target bean, simply define the target inline --></span> <property name="target"> <bean class="com.mycompany.Person"> <span class="lineannotation" style="font-style: italic;"><!-- this is the inner bean --></span> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
注意:内部bean中的singleton标记及id或name属性将被忽略。内部bean总是匿名的且它们总是prototype模式的。同时将内部bean注入到包含该内部bean之外的bean是不可能的。