原文链接:docs.spring.io/spring-fram…
1.4 依赖
典型的企业应用程序不是由单个对象(或者用Spring的说法是bean)组成的。即使是最简单的应用程序也有一些共同工作的对象,以呈现终端用户所认为的一致的应用程序。这一节将解释如何从定义大量独立的bean定义过渡到一个完全实现的应用程序,其中对象通过协作实现目标。
1.4.1 依赖注入
对象只能通过构造函数、工厂方法的参数或set方法来定义它们的依赖关系,容器在创建bean时注入这些依赖项。这个过程基本上是bean直接调用new方法或诸如Service Locator模式这样的机制来控制其依赖项的实例化的逆过程(因此得名“控制反转”)。
使用依赖注入代码会更清晰,当对象与它们的依赖项一起提供时,解耦会更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖项是接口或抽象类时,这允许在单元测试中使用模拟实现。
依赖注入存在于这两种情况: Constructor-based dependency injection 和 Setter-based dependency injection.
基于构造器的依赖注入
基于构造函数的注入是通过容器调用带有参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造bean与构造函数几乎是等价的,本文以类似的方式处理构造函数的参数和静态工厂方法的参数。下面的例子展示了一个只能通过构造函数注入进行依赖注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
这个类没有什么特别之处。它是一个不依赖于容器特定接口、基类或注释的POJO。
构造函数参数解析
构造函数参数解析匹配是通过使用参数的类型来实现的。如果bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当的构造函数。考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设ThingTwo和ThingThree类没有继承关系,就不存在潜在的歧义。因此,以下配置可以正常工作,并且不需要在元素中显式指定构造函数参数索引或类型。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
在上述案例中,依赖项是一个类型已知的bean,spring可以进行匹配。但当使用简单类型时,例如true, Spring不能确定值的类型,因此在没有帮助的情况下不能按类型匹配。考虑以下类:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final 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>
除了解决多个简单值的歧义外,指定索引还解决了构造函数具有两个相同类型参数的歧义。
注意:下标从0开始
按参数名称匹配
你也可以使用构造函数的参数名来消除值的歧义,如下面的例子所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要使其可用,您的代码必须在启用调试标志的情况下编译,以便Spring可以从构造函数中获取参数名。如果不能或不想使用调试标志编译代码,可以使用@ConstructorProperties JDK注解显式地命名构造函数参数。样例类应该如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter方法的依赖注入
基于setter的依赖注入是通过容器在调用无参数构造函数或无参数静态工厂方法来实例化bean之后调用bean上的setter方法来完成的。
下面的例子展示了一个只能通过使用纯setter注入来进行依赖注入的类。这个类是一个不依赖于容器特定接口、基类或注释的传统POJO类。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
<bean id="exampleBean" class="examples.SimpleMovieLister">
<property name="movieFinder" ref=movieFinder/>
</bean>
ApplicationContext为它管理的bean支持基于构造函数和基于setter方式的依赖注入。在通过构造函数方法注入了一些依赖项之后,它还支持基于setter的依赖注入。您可以以BeanDefinition的形式配置依赖项。但是大多数Spring用户并不是通过编程方式配置,而是使用XML bean定义、带注释的组件(即使用@Component、@Controller等带注释的类),或者基于java的@Configuration类中的@Bean方法配置。然后,这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。
由于可以混合使用基于构造函数和基于setter的依赖注入,因此对于强制依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法是一条很好的经验法则。注意,在setter方法上使用@Required注释可以使该属性成为必需的依赖项;然而,使用编程式验证参数的构造函数注入依赖更为可取。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象并确保所需的依赖项不为空。此外,构造函数注入的依赖总是在完全初始化状态下返回给客户端代码。顺便说一下,大量构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的职责,应该进行重构以更好地解决关注事项的适当分离。
Setter注入主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值,否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象能够在以后重新配置或重新注入。
使用对特定类最有意义的依赖注入风格。有时,当您处理没有源代码的第三方类时,您可以自行选择注入方式;如果第三方类没有公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。
依赖解析的过程
容器执行bean依赖关系解析如下:
-
根据bean的配置元数据创建并初始化ApplicationContext。配置元数据可以通过XML、Java代码或注释指定。
-
对于每个bean,它的依赖关系以构造函数参数或静态工厂方法的参数的形式表示。当实际创建bean时,这些依赖项被提供给bean。
-
每个属性或构造函数参数都是实际值或是容器中另一个bean的引用。
-
作为设置值而非引用的属性或构造函数参数,都将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring以字符串格式提供的值转换为所有实际类型类型,比如int、long、string、boolean等等。
Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,不会设置bean属性本身。配置为singleton-scoped (默认) 作用域的bean会在容器创建的时候实例化,否则仅在请求bean时才创建它。创建一个bean可能会导致创建一个bean链,因为bean存在依赖项及其依赖项的依赖项(等等)。请注意,这些依赖项之间的解析不匹配可能会在后期出现——即在受影响bean的第一次创建时出现。
循环依赖
如果主要使用构造函数注入,可能会创建不可解析的循环依赖项场景。
例如:类A通过构造函数注入需要类B的一个实例,类B通过构造函数注入需要类A的一个实例。如果您将类A和类B的bean配置为相互注入,那么Spring IoC容器将在运行时检测这个循环引用,并抛出beancurcurrentlyincreationexception。
一种可能的解决方案是由setter注入。避免构造函数注入,只使用setter注入。换句话说,尽管不推荐这样做,但您可以使用setter注入配置循环依赖项。
与典型情况(没有循环依赖项)不同,bean a和bean B之间的循环依赖项迫使一个bean在完全初始化自己之前被注入到另一个bean中(典型的先有鸡还是先有蛋的场景)。
您通常可以相信Spring会做正确的事情。它在容器加载时检测配置问题,比如对不存在的bean和循环依赖项的引用。当bean实际创建时,Spring会尽可能晚地设置属性并解析依赖项。这意味着,如果在创建对象或其某个依赖项时出现问题,请求对象时,正确加载的Spring容器才会生成异常——例如,例如bean会由于缺少或无效的属性而抛出异常。这可能会延迟一些配置问题的可见性,这就是为什么ApplicationContext实现默认情况下会预先实例化的singleton-scoped bean。在实际需要这些bean之前创建这些bean需要花费一些前期时间和内存,但是可以尽早的发现配置元数据存在的问题,您可以覆盖这个默认行为,以便单例bean可以延迟初始化,而不是急切地预先实例化。
如果不存在循环依赖项,那么当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前都会被完全配置。这意味着,如果bean A依赖于bean B, Spring IoC容器在调用bean A的setter方法之前完全配置bean B。换句话说,bean被实例化,它的依赖项就会被设置,并且调用相关的生命周期方法。
依赖注入的例子
下面的示例为基于xml的配置元数据setter依赖注入的使用。Spring XML配置文件的一小部分指定了一些bean定义,如下所示:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示了相应的ExampleBean类:
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文件中指定的属性相匹配。下面的例子使用了基于构造函数的DI:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示了相应的ExampleBean类:
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="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面的例子显示了相应的ExampleBean类:
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 createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
静态工厂方法的参数是由元素提供的,就像实际使用了构造函数一样。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中是相同的)。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用工厂bean属性而不是class属性),所以我们在这里不讨论这些细节。
1.4.2 依赖和配置细节
如前一节所述,可以将bean属性和构造函数参数定义为对其他协作者的引用或值。Spring基于xml的配置元数据为此提供和元素作为支持。
直接值(整型、字符串等)
元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring的转换服务用于将这些值从String转换为属性或参数的实际类型。下面的示例显示了正在设置的各种值:
<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"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
下面的例子使用p-namespace来进行更简洁的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
您也可以以java.util.Properties的配置方式配置值,如下所示:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
通过使用JavaBeans的PropertyEditor机制,Spring容器将元素中的文本转换为java.util.Properties实例。这是一个很好的快捷方式,也是Spring团队喜欢使用嵌套元素而不是value属性样式的少数地方之一。
idref元素
idref元素只是一种将容器中另一个bean的id传递给或元素的方法。下面的例子展示了如何使用它:
<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"/>
</bean>
第一种形式比第二种形式更可取,因为使用idref标记可以让容器在部署时验证所引用的已命名bean实际存在。在第二个变体中,对传递给客户机bean的targetName属性的值不执行任何验证。只有在实际实例化客户端bean时才会发现(比如输入错误)。如果客户端bean不是在容器创建时实例化,那么这个输入错误和由此产生的异常可能要在容器部署很久之后才会被发现。
对其他bean的引用
ref元素是或定义元素中的最后一个元素。在这里,您将一个bean的指定属性的值设置为对容器管理的另一个bean的引用。被引用的bean是要设置其属性的bean的依赖项,在设置属性之前,根据需要对其进行初始化。(如果合作者是一个单例bean,它可能已经被容器初始化了)所有引用最终都是对另一个对象的引用。
<ref bean="someBean"/>
内部类
或元素中的元素定义了一个内部bean,如下所示:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean定义不需要定义ID或名称。如果指定了该值,容器将不使用该值作为标识符。容器在创建时也会忽略范围标志,因为内部bean总是匿名的,并且总是与外部bean一起创建的。不可能单独访问内部bean,也不可能将它们注入协作bean中。
集合
, , ,和元素分别设置Java集合类型list、set、map和properties的属性和参数。下面的例子展示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</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="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</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>
map键或值、set的值也可以是以下元素中的任何一个:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器还支持合并集合。应用程序开发人员可以定义父元素、、或,并让子元素、、或继承和覆盖父元素集合中的值。也就是说,子集合的值是父集合和子集合的元素合并的结果。
关于这一节中讨论的父-子bean机制。不熟悉父bean和子bean定义的读者请阅读docs.spring.io/spring-fram…
下面的例子演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意在子bean定义的adminEmails属性的元素上使用了merge=true属性。当容器解析并实例化子bean时,结果实例有一个adminEmails Properties集合,该集合包含将子bean的adminEmails集合与父bean的adminEmails集合合并的结果。包含的元素如下所示:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子属性集合的值集继承了父属性的所有属性元素,并且子属性的support值覆盖了父集合中的值。
这种合并行为同样适用于、和集合类型。在元素的特定情况下,将维护与list集合类型(即值的有序集合的概念)相关联的语义,父列表的值在所有子列表的值之前。对于Map、Set和Properties集合类型不存在排序,因此,对于容器内部使用的父-子Map、Set和Properties集合类型是无序的。
集合合并的限制
不能合并不同的集合类型(例如Map和List)。如果您试图这样做,则抛出一个适当的Exception。必须子定义上指定merge属性。在父集合定义上指定merge属性是多余的,并且不会导致所需的合并。
强类型校验集合
在Java 5中引入了泛型类型,导致可以使用强类型集合。也就是说,可以声明一个Collection类型,使它只能包含(例如)String元素。如果使用Spring将强类型Collection依赖注入到bean中,那么可以利用Spring的类型转换支持,在将强类型Collection实例的元素添加到Collection之前,将其转换为适当的类型。下面的Java类和bean定义展示了如何做到这一点:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当准备注入something bean的accounts属性时,关于强类型Map<String, Float>的元素类型的泛型信息可以通过反射获得。因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。
Null值和空值
Spring将空字符串视为空参数。以下基于xml的配置元数据片段将email属性设置为空值("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上述示例相当于以下Java代码:
exampleBean.setEmail("");
元素处理NULL值。下面的清单显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述示例相当于以下Java代码:
exampleBean.setEmail(null);
p-namespace
p-namespace允许您使用bean元素的属性(而不是元素)来描述协作bean的依赖项。
Spring支持基于XML Schema定义的名称空间的可扩展配置格式。本章讨论的bean配置格式是在XML Schema文档中定义的。但是,p-namespace并没有在XSD文件中定义,它只存在于Spring的核心中。
下面的例子显示了两个XML片段(第一个使用标准XML格式,第二个使用p-namespace),它们达到相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
这个示例不仅包含使用p-namespace的属性值,而且还使用一种特殊的格式来声明属性引用。第一个bean定义使用来创建从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref="jane"完成完全相同的工作。在本例中,spouse是属性名,而-ref部分表示这不是一个直接值,而是对另一个bean的引用。
p-namespace不像标准XML格式那样灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式不会。我们建议您谨慎地选择,并与您的团队成员进行沟通,以避免生成同时使用多种方法的XML文档。
c-namespace
与带有p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace作为的等效品来配置构造函数参数,下面的例子使用c: namespace来实现基于构造函数的依赖注入:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
对于构造函数参数名不可用的罕见情况(通常是在只有字节码的情况下),你可以使用回退参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
由于XML语法的原因,索引表示法要求出现前导_,因为XML属性名不能以数字开头(尽管有些ide允许)。
在实践中,构造函数解析机制在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称表示法。
复合属性
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最终属性名除外)不为空。考虑以下bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
这个bean有fred变量、fred变量中有个bob变量、bob变量中有个sammy变量,sammy变量值被设置为123。为了使其工作,在构造bean之后,某物的fred属性和fred的bob属性不能为空。否则,抛出NullPointerException。
1.4.3 使用 depends-on
如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。通常使用基于xml的配置元数据中的元素来实现这一点。但是,有时bean之间的依赖关系不是那么直接。例如,当需要触发类中的静态代码块时(例如数据库驱动程序注册),depends-on可以显式地在使用此元素的bean初始化之前强制初始化一个或多个bean。下面的例子使用depends-on属性来表示对单个bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖,提供一个bean列表作为depends-on属性的值(逗号、空格和分号都是有效的分隔符):
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on属性可以指定初始化时的依赖项,在单例bean的情况下,还可以指定相应的销毁时依赖项。定义了与给定bean的依赖关系的依赖bean首先被销毁,然后是给定bean本身被销毁。因此,此依赖还可以控制销毁顺序。
1.4.4 懒加载
默认情况下,ApplicationContext实现会在初始化过程中创建和配置所有单例bean。通常,这种预实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后才发现。当这种行为不可取时,您可以通过将bean定义标记为惰性初始化来防止单例bean的预实例化。延迟初始化bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,这种行为由元素上的lazy-init属性控制,如下面的例子所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.something.AnotherBean"/>
当上述配置被ApplicationContext使用时,当ApplicationContext启动时,lazy-init bean不会被急切地预实例化,not.lazy bean被急切地预先实例化。
然而,当一个惰性初始化的bean是一个非惰性初始化的单例bean的依赖项时,ApplicationContext会在启动时创建惰性初始化的bean,惰性初始化的bean被实例化后注入到未惰性初始化的单例bean中。
你也可以通过在元素上使用default-lazy-init属性来控制容器级的惰性初始化,如下面的例子所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5 自动装配
Spring容器可以自动装配协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring自动为您的bean解析协作者(其他bean)。自动装配具有以下优点:
- 自动装配可以显著减少指定属性或构造函数参数的需要。(本章其他地方讨论的其他机制,如bean模板,在这方面也很有价值。)
- 自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,在开发过程中,自动装配尤其有用。
当使用基于xml的配置元数据时(请参阅依赖注入),您可以使用元素的autotowire属性为bean定义指定自动装配模式。自动装配功能有四种模式。您可以动态指定每个bean的自动装配模式。下表描述了四种自动装配模式:
Mode | Explanation |
---|---|
no | (默认)禁止自动装配。Bean引用必须由' ref '元素定义。对于较大的部署,不建议更改默认设置,因为明确指定协作者可以提供更大的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 通过属性名自动装配。Spring查找与需要自动连接的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配,并且它包含一个主属性(也就是说,它有一个setMaster(..)方法),Spring会寻找一个名为master的bean定义,并使用它来设置该属性。 |
byType | 如果容器中正好存在一个属性类型的bean,则让属性自动连接。如果存在多个,则抛出致命异常,这表明您不能为该bean使用byType自动装配。如果没有匹配的bean,则不会发生任何事情(属性没有设置)。 |
constructor | 类似于'byType ,但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,则会引发致命错误。 |