典型的企业应用程序不是由单个对象(或者Spring术语中的bean)组成的。即使是最简单的应用程序也有一些对象一起工作,以呈现最终用户视为一致的应用程序。下一节将解释如何从定义大量独立的bean定义到完全实现的应用程序(其中对象协作实现目标)。
1.4.1. 依赖注入
依赖注入(DI)是一个过程,在这个过程中,对象仅通过构造函数参数、工厂方法的参数或在构造或从工厂方法返回对象实例后在对象实例上设置的属性来定义它们的依赖关系(即与它们一起工作的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程本质上是bean本身通过直接构造类或Service Locator模式控制其依赖项的实例化或位置的逆过程(因此称为控制反转)。
使用DI原则的代码更简洁,并且当对象具有依赖关系时,解耦更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系是在接口或抽象基类上时,它们允许在单元测试中使用存根或模拟实现。
依赖注入有两种主要形式:基于构造函数的依赖注入和基于setter的依赖注入。
基于构造函数的依赖注入
基于构造函数的DI是通过容器调用带有许多参数的构造函数来完成的,每个参数表示一个依赖项。调用带有特定参数的静态工厂方法来构造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...
}
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。如果bean定义的构造函数参数中不存在潜在的歧义,则在bean定义中定义构造函数参数的顺序就是实例化bean时将这些参数提供给适当构造函数的顺序。考虑下面的类:
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时,类型是已知的,可以进行匹配(就像前面的示例一样)。当使用简单类型时,例如<value>true</value>, Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。考虑下面的类:
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>
除了解决多个简单值的模糊性外,指定索引还可以解决构造函数有两个相同类型的参数时的模糊性。
你也可以使用构造函数参数名来消歧,如下面的例子所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要使此工作开箱即用,必须在编译代码时启用调试标志,以便Spring可以从构造函数中查找参数名。如果不能或不想使用调试标志编译代码,可以使用@ConstructorProperties JDK注释显式地命名构造函数参数。样例类必须看起来如下所示:
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于setter的依赖注入
基于setter的DI是通过容器在调用无参数构造函数或无参数静态工厂方法实例化bean之后调用bean上的setter方法来实现的。
下面的示例显示了一个只能通过使用纯setter注入进行依赖项注入的类。这个类是传统的Java。它是一个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...
}
ApplicationContext对它管理的bean支持基于构造函数和基于setter的DI。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。以BeanDefinition的形式配置依赖项,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML bean定义、带注释的组件(即用@Component、@Controller等标注的类)或基于java的@Configuration类中的@Bean方法。然后在内部将这些源转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。
基于构造函数的DI还是基于设置的DI?
由于您可以混合使用基于构造函数和基于setter的DI,因此对于强制性依赖项使用构造函数,对于可选依赖项使用setter方法或配置方法是一个很好的经验法则。注意,在setter方法上使用@Autowired注释可以使属性成为必需的依赖项;但是,构造函数注入和参数的编程验证更可取。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为旁注,大量的构造函数参数是一种糟糕的代码气味,这意味着类可能有太多的责任,应该重构以更好地解决适当的关注点分离。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是setter方法使该类的对象以后可以重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。
依赖关系解决过程
容器执行bean依赖解析如下:
- 使用描述所有bean的配置元数据创建和初始化ApplicationContext。配置元数据可以由XML、Java代码或注释指定。
- 对于每个bean,它的依赖关系都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果您使用静态工厂方法而不是常规构造函数)。在实际创建bean时,将向bean提供这些依赖项。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 作为值的每个属性或构造函数实参都将从其指定格式转换为该属性或构造函数实参的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,例如int、long、string、boolean等等。
Spring容器在创建容器时验证每个bean的配置。但是,直到实际创建bean时才设置bean属性本身。在创建容器时创建单例作用域并设置为预实例化(默认值)的bean。作用域在Bean作用域中定义。否则,只有在请求bean时才创建它。创建bean可能会导致创建bean图,因为bean的依赖项及其依赖项的依赖项(等等)会被创建和分配。请注意,这些依赖项之间的解析不匹配可能会在较晚的时候出现——也就是说,在第一次创建受影响的bean时。
循环依赖
如果你主要使用构造函数注入,就有可能创建一个无法解决的循环依赖场景。
例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果将类A和类B的bean配置为相互注入,则Spring IoC容器将在运行时检测到此循环引用,并抛出BeanCurrentlyInCreationException。
一种可能的解决方案是编辑某些类的源代码,使其由setter而不是构造函数配置。或者,避免构造函数注入,只使用setter注入。换句话说,尽管不建议这样做,但您可以使用setter注入配置循环依赖项。
与典型的情况(没有循环依赖关系)不同,bean a和bean B之间的循环依赖关系迫使一个bean在完全初始化之前被注入到另一个bean中(经典的先有鸡还是先有蛋的场景)。
您通常可以相信Spring会做正确的事情。它在容器加载时检测配置问题,例如对不存在的bean的引用和循环依赖。Spring尽可能晚地设置属性并解析依赖项,即当bean实际创建时。这意味着,如果在创建对象或其依赖项时出现问题,那么正确加载的Spring容器稍后可以在请求对象时生成异常——例如,由于缺少或无效的属性,bean会抛出异常。这可能会延迟一些配置问题的可见性,这就是为什么ApplicationContext实现默认预实例化单例bean的原因。在实际需要这些bean之前创建这些bean需要花费一些前期时间和内存,因此您可以在创建ApplicationContext时发现配置问题,而不是在创建ApplicationContext之后。您仍然可以重写此默认行为,以便惰性地初始化单例bean,而不是急切地预实例化单例bean。
如果不存在循环依赖关系,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在注入到依赖bean之前都已完全配置好。这意味着,如果bean A依赖于bean B,则Spring IoC容器在调用bean A上的setter方法之前完全配置bean B。换句话说,bean被实例化(如果它不是预实例化的单例),它的依赖项被设置,相关的生命周期方法(例如已配置的init方法或InitializingBean回调方法)被调用。
依赖注入的例子
下面的示例将基于xml的配置元数据用于基于setter的DI。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"/>
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"/>
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"/>
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;
}
}
静态工厂方法的参数由<constructor-arg/>元素提供,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在本例中,它是相同的)。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用factory-bean属性而不是class属性之外),因此我们在这里不讨论这些细节。
1.4.2. 依赖性和配置的详细信息
如前一节所述,可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者定义为内联定义的值。Spring基于xml的配置元数据支持<property/>和<constructor-arg/>元素中的子元素类型。
直接值(原语、字符串等)
元素的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-命名空间进行更简洁的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>
上面的XML更简洁。但是,错字是在运行时而不是设计时发现的,除非您使用支持在创建bean定义时自动完成属性的IDE(如IntelliJ IDEA或用于Eclipse的Spring Tools)。强烈推荐这样的IDE协助。
你也可以配置一个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>
Spring容器使用JavaBeans的PropertyEditor机制将<value/>元素中的文本转换为java.util.Properties实例。这是一个很好的快捷方式,也是Spring团队倾向于使用嵌套<value/>元素而不是value属性样式的少数几个地方之一。
idref元素
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"/>
</bean>
第一种形式比第二种形式更可取,因为使用idref标记可以让容器在部署时验证引用的命名bean是否确实存在。在第二种变化中,没有对传递给客户端bean的targetName属性的值执行验证。只有在实际实例化客户端bean时才会发现错字(极有可能导致致命的结果)。如果客户端bean是一个原型bean,那么这个错字和由此产生的异常可能只有在容器部署很久之后才会被发现。
4.0 bean XSD中不再支持idref元素上的local属性,因为它不再通过常规bean引用提供值。升级到4.0模式时,将现有的idref本地引用更改为idref bean。
元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBean bean定义中的AOP拦截器配置中。在指定拦截器名称时使用元素可以防止拼写错误的拦截器ID。
对其他bean的引用(合作者)
ref元素是<constructor-arg/>或<property/>定义元素中的最后一个元素。在这里,您将bean的指定属性的值设置为对容器管理的另一个bean(协作者)的引用。被引用的bean是要设置其属性的bean的依赖项,并且在设置属性之前根据需要对其进行初始化。(如果合作者是一个单例bean,它可能已经被容器初始化了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于您是否通过bean或父属性指定了其他对象的ID或名称。
通过标记的bean属性指定目标bean是最通用的形式,允许在同一个容器或父容器中创建对任何bean的引用,而不管它是否在同一个XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的某个值相同。下面的例子展示了如何使用ref元素:
< ref bean = " someBean " / >
通过父属性指定目标bean将创建对当前容器的父容器中的bean的引用。父属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。目标bean必须位于当前bean的父容器中。您应该使用这个bean引用变体,主要是当您有一个容器层次结构,并且您想用一个与父bean同名的代理将一个现有的bean包装在父容器中。下面的一对清单显示了如何使用父属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
4.0 bean XSD中不再支持ref元素上的local属性,因为它不再提供常规bean引用的值。升级到4.0模式时,将现有的ref本地引用更改为ref bean。
内部beans/Inner Beans
在<property/>或<constructor-arg/>元素中的元素定义了一个内部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(而不是封闭bean)。
作为一种极端情况,可以从自定义作用域接收销毁回调——例如,对于包含在单例bean中的请求作用域内部bean。内部bean实例的创建与包含它的bean绑定在一起,但是破坏回调让它参与到请求作用域的生命周期中。这种情况并不常见。内部bean通常只是共享其包含bean的作用域。
集合/Collection
<list/>、<set/>、<map/>和<props/>元素分别设置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 key或value或set value的值也可以是以下元素中的任何一个:
Bean | ref | idref | list | set | map | props | value | null
合并集合/又称为父子集合
Spring容器还支持合并集合。应用程序开发人员可以定义父元素<list/>、<map/>、<set/>或<props/>,并让子元素<list/>、<map/>、<set/>或<props/>继承和重写父集合的值。也就是说,子集合的值是合并父集合和子集合元素的结果,子集合元素覆盖父集合中指定的值。
关于合并的这一节讨论了父子bean机制。不熟悉父bean和子bean定义的读者可能希望在继续之前阅读相关部分。
<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定义的adminemail属性的元素上使用了merge=true属性。当容器解析并实例化子bean时,生成的实例具有一个adminemail Properties集合,其中包含合并子bean的adminemail集合与父bean的adminemail集合的结果。下面的清单显示了结果:
子属性集合的值集继承父元素中的所有属性元素,子元素的支持值将覆盖父集合中的值。
这种合并行为类似于、和集合类型。在元素的特定情况下,维护了与list集合类型相关的语义(即值的有序集合的概念)。父级列表的值位于所有子级列表的值之前。对于Map、Set和Properties集合类型,不存在顺序。因此,对于容器内部使用的关联Map、Set和Properties实现类型基础上的集合类型,没有任何排序语义生效。
集合合并的局限性
您不能合并不同的集合类型(例如Map和List)。如果您尝试这样做,则会抛出适当的异常。merge属性必须在较低的继承的子定义上指定。在父集合定义上指定merge属性是多余的,并且不会导致所需的合并。
强类型集合
由于Java对泛型类型的支持,您可以使用强类型集合。也就是说,可以声明一个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的配置元数据片段将电子邮件属性设置为空String值("")。
//设置成空字符串 exampleBean.setEmail("");
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
//exampleBean.setEmail(null);
<property name="email"> <null/> </property> </bean>
使用p-命名空间的XML快捷方式
p-命名空间允许您使用bean元素的属性(而不是嵌套的元素)来描述协作bean的属性值,或者同时使用两者。
Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML Schema定义。本章讨论的bean配置格式是在XML Schema文档中定义的。但是,p-命名空间并不是在XSD文件中定义的,它只存在于Spring的核心中。
下面的示例显示了解析到相同结果的两个XML片段(第一个使用标准XML格式,第二个使用p名称空间):
<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="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
该示例显示了bean定义中p-命名空间中名为email的属性。这告诉Spring包含一个属性声明。如前所述,p-命名空间没有模式定义,因此可以将属性名设置为属性名。
下一个例子包含了另外两个bean定义,它们都有对另一个bean的引用:
<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-命名空间的属性值,还使用特殊格式声明属性引用。尽管第一个bean定义使用来创建从bean john到bean jane的引用,但第二个bean定义使用p:spouse-ref="jane"作为属性来完成完全相同的事情。在本例中,spouse是属性名,而-ref部分表示这不是一个直接的值,而是对另一个bean的引用。
p-名称空间不像标准XML格式那样灵活。例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不会。我们建议您仔细选择您的方法,并与您的团队成员进行沟通,以避免生成同时使用这三种方法的XML文档。
使用c-命名空间的XML快捷方式
与带有p-namespace的XML快捷方式类似,在Spring 3.1中引入的c-namespace允许配置构造函数参数的内联属性,而不是嵌套的构造函数参数元素。
下面的例子使用c:命名空间来做与from基于构造函数的依赖注入相同的事情:
<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:使用与p: one (bean引用的尾随-ref)相同的约定来根据构造函数参数的名称设置参数。类似地,它需要在XML文件中声明,即使它没有在XSD模式中定义(它存在于Spring核心中)。
对于构造函数参数名不可用的罕见情况(通常是在没有调试信息的情况下编译字节码),您可以使用回退到参数索引,如下所示:
<!-- 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允许这样做)。对应的索引符号也可用于
<constructor-arg>元素,但不常用,因为普通的声明顺序通常就足够了。
在实践中,构造函数解析机制在匹配参数方面非常有效,所以除非您确实需要,否则我们建议在整个配置中使用name符号。
复合属性名
在设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(除了最终属性名)不为空。考虑下面的bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something bean有一个fred属性,它有一个bob属性,它有一个sammy属性,最后的sammy属性被设置为123。为了实现这一点,在构造bean之后,something的fred属性和fred的bob属性不能为空。否则,抛出NullPointerException。
1.4.3. 使用depends-on
如果一个bean是另一个bean的依赖项,这通常意味着一个bean被设置为另一个bean的属性。通常使用基于xml的配置元数据中的<ref/>元素来实现这一点。然而,有时bean之间的依赖关系不那么直接。例如,当类中的静态初始化式需要触发时,例如数据库驱动程序注册。depends属性可以显式地强制在初始化使用此元素的bean之前初始化一个或多个bean。下面的例子使用dependencies属性来表示对单个bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表达对多个bean的依赖关系,提供一个bean名称列表作为dependencies属性的值(逗号、空格和分号是有效的分隔符):
<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" />
ependencies属性既可以指定初始化时依赖项,也可以指定对应的销毁时依赖项(仅在单例bean的情况下)。与给定bean定义依赖关系的依赖bean首先被销毁,然后再销毁给定bean本身。因此,依赖也可以控制关机顺序。
1.4.4。延迟初始化的bean
默认情况下,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使用时,懒bean在ApplicationContext启动时不会急切地预实例化,而不是。Lazy bean被急切地预实例化。
然而,当惰性初始化bean是未惰性初始化的单例bean的依赖项时,ApplicationContext在启动时创建惰性初始化bean,因为它必须满足单例的依赖项。惰性初始化的bean被注入到其他地方没有惰性初始化的单例bean中。
您还可以通过在<beans/>元素上使用default-lazy-init属性来控制容器级别的惰性初始化,如下面的示例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动装配的合作者
Spring容器可以自动连接协作bean之间的关系。您可以让Spring通过检查ApplicationContext的内容来为您的bean自动解析协作者(其他bean)。自动装配有以下优点:
- 自动装配可以显著减少指定属性或构造函数参数的需要。(在这方面,本章其他地方讨论的bean模板等其他机制也很有价值。)
- 自动装配可以随着对象的演变而更新配置。例如,如果您需要向类添加一个依赖项,那么该依赖项可以自动得到满足,而不需要修改配置。因此,在开发过程中,自动装配尤其有用,当代码库变得更加稳定时,无需放弃切换到显式连接的选项。
当使用基于xml的配置元数据时(请参阅依赖注入),您可以使用<bean/>元素的autowire属性为bean定义指定自动连接模式。自动装配功能有四种模式。您指定了每个bean的自动装配,从而可以选择要自动装配的bean。四种自动装配模式如下表所示:
| Mode | Explanation |
|---|---|
no | (默认值)不自动装配。Bean引用必须由' ref '元素定义。对于较大的部署,不建议更改默认设置,因为显式指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 通过属性名自动装配。Spring寻找与需要自动连接的属性具有相同名称的bean。例如,如果一个bean定义被设置为按名称自动装配,并且它包含一个' master '属性(也就是说,它有一个' setMaster(..) '方法),Spring会寻找一个名为' master '的bean定义并使用它来设置该属性。 |
byType | 如果容器中仅存在一个属性类型的bean,则允许自动连接属性。如果存在多个,则抛出致命异常,这表明您不能对该bean使用' byType '自动装配。如果没有匹配的bean,则什么都不会发生(属性未设置)。 |
constructor | 类似于' byType ',但适用于构造函数参数。如果容器中没有一个构造函数参数类型的bean,则会引发致命错误 |
使用byType或构造函数自动装配模式,可以连接数组和类型化集合。在这种情况下,将提供容器内与预期类型匹配的所有自动装配候选项以满足依赖关系。如果期望的键类型是String,则可以自动装配强类型Map实例。自动连接的Map实例的值由所有匹配预期类型的bean实例组成,Map实例的键包含相应的bean名称。
自动装配的局限性和缺点
Autowiring在整个项目中始终如一地使用时效果最佳。如果通常不使用自动装配,那么仅使用它来连接一个或两个bean定义可能会使开发人员感到困惑。
考虑自动装配的局限性和缺点:
-
属性和构造函数参数设置中的显式依赖总是覆盖自动装配。您不能自动装配简单的属性,如原语、字符串和类(以及这些简单属性的数组)。这种限制是设计出来的。
-
自动装配不如显式连接准确。尽管如前面的表中所指出的,Spring小心地避免猜测,以免出现可能产生意外结果的歧义。spring管理对象之间的关系不再显式地记录。
-
连接信息对于可能从Spring容器生成文档的工具来说可能不可用。
-
容器内的多个bean定义可能与setter方法或构造函数参数指定的类型匹配。对于数组、集合或Map实例,这并不一定是个问题。然而,对于需要单个值的依赖项,这种模糊性不会被任意解决。如果没有可用的惟一bean定义,则抛出异常。
在后一种情况下,你有几个选择:
-
放弃自动装配,支持显式连接。
-
通过将bean的autowire-candidate属性设置为false,避免对bean定义进行自动装配,如下一节所述。
-
通过将元素的主属性设置为true,将单个bean定义指定为主要候选bean。
-
实现基于注释的配置提供的更细粒度的控制,如基于注释的容器配置中所述。
从自动装配中排除Bean
在每个bean的基础上,您可以从自动装配中排除一个bean。在Spring的XML格式中,将<bean/>元素的autowire-candidate属性设置为false。容器使特定的bean定义对自动装配基础设施(包括@Autowired之类的注释样式配置)不可用。
autowire-candidate属性被设计为仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的bean没有被标记为自动装配候选对象,引用也会被解析。因此,根据名称自动装配仍然会在名称匹配时注入bean。
您还可以根据bean名称的模式匹配限制自动装配候选对象。顶层的<beans/>元素在其默认的autowire-candidate属性中接受一个或多个模式。例如,要将自动装配候选状态限制为名称以Repository结尾的任何bean,请提供值*Repository。若要提供多个模式,请在逗号分隔的列表中定义它们。bean定义的autowire-candidate属性的显式值true或false总是优先。对于这样的bean,模式匹配规则不适用。
这些技术对于您不希望通过自动装配将bean注入到其他bean中的bean非常有用。这并不意味着被排除的bean本身不能通过使用自动装配来配置。更确切地说,bean本身不是自动装配其他bean的候选者。
1.4.6. 方法注射
在大多数应用程序场景中,容器中的大多数bean都是单例bean。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,问题就出现了。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每次方法调用上。容器只创建一次单例bean A,因此只有一次设置属性的机会。容器不能在每次需要时都为bean A提供bean B的新实例。
一个解决办法是放弃某些控制反转。您可以通过实现ApplicationContextAware接口,以及在bean A每次需要bean B实例时调用容器的getBean(“B”)来让bean A知道容器。下面的例子展示了这种方法:
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的情况是不可取的,因为业务代码知道Spring框架并与之耦合。方法注入是Spring IoC容器的一种高级特性,它允许您干净地处理这个用例。
你可以在这篇博客文章中读到更多关于方法注入的动机。
查找方法注入
查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及一个原型bean,如前一节中描述的场景所示。Spring框架通过使用来自CGLIB库的字节码生成来动态生成覆盖该方法的子类,从而实现了这种方法注入。
- 要使这种动态子类化工作,Spring bean容器子类化的类不能是final的,要重写的方法也不能是final的。
- 对具有抽象方法的类进行单元测试需要您自己创建类的子类,并提供抽象方法的存根实现。
- 具体的方法对于组件扫描也是必要的,这需要具体的类来获取。
- 进一步的关键限制是,查找方法不能与工厂方法一起工作,特别是不能与配置类中的@Bean方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态地创建运行时生成的子类。
对于前面代码片段中的CommandManager类,Spring容器动态地覆盖createCommand()方法的实现。CommandManager类没有任何Spring依赖项,如重做的示例所示:
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入方法的客户端类(在本例中为CommandManager)中,要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑下面的例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用它自己的createCommand()方法。如果确实需要myCommand bean,则必须谨慎地将其部署为原型。如果它是一个单例,每次都会返回相同的myCommand bean实例。
或者,在基于注释的组件模型中,你可以通过@Lookup注释声明一个查找方法,如下面的例子所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更习惯地说,您可以依赖于目标bean根据查找方法声明的返回类型进行解析:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
注意,您通常应该使用具体的存根实现来声明这样的带注释的查找方法,以便它们与Spring的组件扫描规则兼容,默认情况下忽略抽象类。此限制不适用于显式注册或显式导入的bean类。
访问不同作用域的目标bean的另一种方法是ObjectFactory/ Provider注入点。参见作用域bean作为依赖项。
您可能还会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)很有用。
任意方法替换
与查找方法注入相比,方法注入用处较小的一种形式是用另一种方法实现替换托管bean中的任意方法的能力。在真正需要此功能之前,您可以安全地跳过本节的其余部分。
对于基于xml的配置元数据,您可以使用replace -method元素将已部署bean的现有方法实现替换为另一个方法实现。考虑下面的类,它有一个名为computeValue的方法,我们想重写它:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现了methodreplacer接口的类提供了新的方法定义,如下面的例子所示:
/**
* meant to be used to override the existing computeValue(String)
* 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 ...;
}
}
部署原始类并指定方法重写的bean定义类似于以下示例:
<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"/>
您可以在<replace -method/>元素中使用一个或多个<arg-type/>元素来指示被覆盖方法的方法签名。只有当方法重载且类中存在多个变量时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名的子字符串。例如,以下所有匹配java.lang.String:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,这个快捷方式可以节省大量的输入,因为它允许您只输入匹配参数类型的最短字符串。