加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Part III. 核心技术-7. IOC容器-7.4 依赖关系

发布时间:2020-12-14 01:36:08 所属栏目:百科 来源:网络整理
导读:典型的企业应用程序不包含单个对象(或Spring说明中的bean)。 即使是最简单的应用程序,也有几个对象共同合作,展示最终用户看到的一致性应用程序。 下一节将介绍如何从定义一些独立于完全实现的应用程序的bean定义(对象协作实现目标)。 7.4.1 依赖注入

典型的企业应用程序不包含单个对象(或Spring说明中的bean)。 即使是最简单的应用程序,也有几个对象共同合作,展示最终用户看到的一致性应用程序。 下一节将介绍如何从定义一些独立于完全实现的应用程序的bean定义(对象协作实现目标)。

7.4.1 依赖注入

依赖注入(DI)是一个过程,对象定义它们的依赖关系,也就是与它们配合使用的其他对象,只有通过构造函数参数,工厂方法的参数或构造或返回对象实例后设置的属性 从工厂方法。 然后,容器在创建bean时注入这些依赖项。 这个过程基本上是逆向的,因此,控制的控制(IoC)名称本身就是通过使用直接构造类或者服务定位器模式来控制自己的依赖关系的实例化或位置。

使用DI原理,代码更清晰,当对象具有依赖关系时,解耦更有效。 对象不查找其依赖关系,并且不知道依赖关系的位置或类。 因此,您的类变得更容易测试,特别是当依赖关系在接口或抽象基类上时,允许在单元测试中使用存根或模拟实现。

DI存在两种主要的变体:基于构造函数的依赖注入和基于Setter的依赖关系注入。

基于构造函数的依赖注入

基于构造函数的DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖关系。 调用一个具有特定参数的静态工厂方法来构造bean几乎是等效的,这个讨论类似地将参数视为一个构造函数和一个静态的工厂方法。 以下示例显示一个只能使用构造函数注入注入的依赖关系的类。 请注意,这个类没有什么特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。

public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private 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被实例化时将这些参数提供给适当的构造函数的顺序。 考虑下列课程:

package x.y;

Foo {
Foo(Bar bar,Baz baz) {
// ...
}

}

没有潜在的模糊性存在,假设BarBaz类与继承无关。 因此,以下配置工作正常,您不需要在<constructor-arg />元素中显式指定构造函数参数索引和/或类型。

<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>

<bean id="bar" class="x.y.Bar"/>

<bean id="baz" class="x.y.Baz"/>
</beans>

当引用另一个bean时,类型是已知的,并且可以进行匹配(如前面的例子所示)。 当使用简单的类型,例如<value> true </ value>时,Spring无法确定值的类型,因此无法通过类型匹配,无需帮助。 考虑下列课程:

package examples;

ExampleBean {

// Number of years to calculate the Ultimate Answer
private int years;

// The Answer to Life,the Universe,and Everything
private String ultimateAnswer;

ExampleBean(int years,String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}

}

在上述情况下,如果使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的type。 例如:

<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>

使用index属性来明确指定构造函数参数的索引。 例如:

"examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引可以解析构造函数具有两个相同类型参数的歧义。 请注意,索引为0。

您还可以使用构造函数参数名称进行值消除歧义:

"examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,为了使这个工作开箱即用,您的代码必须在启用调试标志的情况下进行编译,以便Spring可以从构造函数中查找参数名称。 如果您无法使用调试标志(或不想)编译代码,则可以使用@ConstructorPropertiesJDK注释来明确命名构造函数参数。 然后样本类必须如下所示:

// Fields omitted

@ConstructorProperties({"years","ultimateAnswer"})
this.ultimateAnswer = ultimateAnswer;
}

基于Setter的依赖注入

基于Setter的DI通过在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean的setter方法来实现。

以下示例显示一个只能使用纯setter注入进行依赖注入的类。 这个类是常规Java。 它是一个POJO,它不依赖容器特定的接口,基类或注释。

public class SimpleMovieLister {

// the SimpleMovieLister has a dependency on the MovieFinder

// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
// business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持基于构造函数和基于setter的DI,用于其管理的bean。 在一些依赖关系已经通过构造方法注入之后,它也支持基于setter的DI。 您可以以BeanDefinition的形式配置依赖项,该属性与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。 然而,大多数Spring用户不直接使用这些类(即以编程方式),而是使用XML bean定义,注释组件(即,使用@Component@Controller等注释的类),或者基于Java的@Bean方法@Configuration类。 然后将这些源内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。

基于构造函数或基于setter的DI?

由于可以混合基于构造函数和基于setter的DI,因此使用构造函数来强制依赖关系和setter方法或可选依赖关系的配置方法是一个>很好的经验法则。 请注意,可以使用setter方法上的@Required注释来使属性成为必需的依赖关系。

Spring团队通常主张构造函数注入,因为它可以将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。 此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。 作为一个附注,大量的构造函数论证是一个坏的代码气味,这意味着该类可能有太多的责任,应该重构以更好地解决问题的正确分离。

Setter注入应主要用于可选依赖关系,可以在类中分配合理的默认值。 否则,必须在代码使用依赖关系的任何地方执行非空检查。 setter注入的一个好处是setter方法使得该类的对象可以在以后重新配置或重新注入。 因此,通过JMX管理MBean是一种引人注目的用例。

使用对特定课程最有意义的DI风格。 有时候,当您处理没有来源的第三方课程时,您可以选择。 例如,如果第三方类不暴露任何>setter方法,那么构造函数注入可能是DI的唯一可用形式。

依赖性解决过程

容器执行如下的bean依赖解析:

  • 使用描述所有bean的配置元数据创建和初始化ApplicationContext。 可以通过XML,Java代码或注释指定配置元数据。

  • 对于每个bean,如果使用它,而不是正常的构造函数,其依赖性以静态工厂方法的属性,构造函数参数或参数的形式表示。 当bean实际创建时,这些依赖关系被提供给bean。

  • 每个属性或构造函数参数是要设置的值的实际定义或引用 容器中另一个bean。

  • 作为值的每个属性或构造函数参数都将从其指定的格式转换为该属性或构造函数参数的实际类型。 默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,如intlongStringboolean等。

创建容器时,Spring容器会验证每个bean的配置。 但是,bean实际创建之前不会设置bean属性。 单个复制并设置为预实例化(默认)的Bean将在创建容器时创建。 范围在7.5节“Bean范围”中定义。 否则,只有在请求时才创建该bean。 创建bean可能会导致创建bean的图形,因为创建和分配了bean的依赖关系及其依赖关系(等等)。 请注意,这些依赖关系中的解决方案不匹配可能会显示较晚,即首次创建受影响的bean。

循环依赖

如果您主要使用构造函数注入,则可以创建一个无法解决的循环依赖性场景。

例如:Class A通过构造函数注入需要一个类B的实例,B类需要一个通过构造函数注入的A类实例。 如果将类A和B的bean配置为彼此注入,则Spring IoC容器会在运行时检测此循环引用,并抛出一个BeanCurrentlyInCreationException。

一个可能的解决方案是编辑要由setter而不是构造函数配置的某些类的源代码。 或者,避免构造器注入和仅使用设定器注入。 换句话说,虽然不推荐,您可以使用setter注入来配置循环依赖。

与典型的情况(没有循环依赖)不同,bean A和bean B之间的循环依赖关系强制其中一个Bean在被完全初始化之前被注入另一个bean(经典鸡/鸡蛋场景)。

你一般可以信任Spring做正确的事情。它在容器加载时检测到配置问题,例如对不存在的bean和循环依赖项的引用。当bean实际创建时,Spring会尽可能早地设置属性并解析依赖关系。这意味着如果在创建该对象或其依赖关系时出现问题,则在请求对象时可以正确加载的Spring容器可以生成异常。例如,bean由于缺少或无效的属性而抛出异常。某些配置问题的这种潜在的延迟可见性是为什么默认情况下,ApplicationContext实现了单例bean之前的实例化。以实际需要之前创建这些bean的一些前期时间和内存为代价,您可以在创建ApplicationContext时发现配置问题,而不是以后。您仍然可以覆盖此默认行为,以便单例bean将进行延迟初始化,而不是预先实例化。

如果不存在循环依赖关系,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入依赖bean之前被完全配置。 这意味着如果bean A对bean B有依赖关系,则Spring IoC容器在调用bean A的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是一个预先实例化的单例),它 设置依赖关系,并调用相关的生命周期方法(如配置的init方法或InitializingBean回调方法)。

依赖注入的例子

以下示例使用基于设置器的DI的基于XML的配置元数据。 Spring XML配置文件的一小部分指定了一些bean定义:

"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 {

private AnotherBean beanOne;
private YetAnotherBean beanTwo;
int i;

setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}

setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}

setIntegerPropertyint i) {
this.i = i;
}
}

在前面的示例中,setter被声明为与XML文件中指定的属性相匹配。以下示例使用基于构造函数的DI:

<!-- 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="examples.YetAnotherBean"/>
ExampleBean {
private AnotherBean beanOne;

private YetAnotherBean beanTwo;

(AnotherBean anotherBean,YetAnotherBean yetAnotherBean,int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}

在bean定义中指定的构造函数参数将被用作构造函数的参数的例子。

现在考虑一个这个例子的变体,而不是使用一个构造函数,Spring被要求调用一个静态工厂方法来返回对象的一个实例:

"examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="examples.AnotherBean"/>
<bean id="examples.YetAnotherBean"/>
ExampleBean {
// a private constructor
private (...) {
...
}

// 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.
static ExampleBean createInstance (
AnotherBean anotherBean,239)">int
i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}

静态工厂方法的参数通过<constructor-arg />元素提供,与实际使用构造函数完全相同。 工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同,尽管在此示例中。 将以基本相同的方式使用实例(非静态)工厂方法(除了使用factory-bean属性而不是class属性),因此这里不再讨论细节。

7.4.2 依赖和配置详细

如上一节所述,您可以将bean属性和构造函数参数定义为对其他受管Bean(协作者)的引用,也可以定义为inline。 Spring的基于XML的配置元数据支持其<property /><constructorarg />元素中的子元素类型。

直接值(基本类型,字符串等)

<property />元素的value属性将一个属性或构造函数参数指定为人类可读的字符串表示形式。 Spring的转换服务用于将这些值从String转换为实际的属性或参数类型。

"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="masterkaoli"/>
</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
http://www.springframework.org/schema/beans/spring-beans.xsd"
>


<bean id="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="masterkaoli"/>


</beans>

前面的XML更简洁; 但是,在运行时而不是设计时发现错字,除非您在创建bean定义时使用支持自动属性完成的IDE(如IntelliJ IDEA或Spring Tool Suite(STS))。 强烈建议您使用此类IDE协助。

您还可以将java.util.Properties实例配置为:

"mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- 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 "theClientBean" "...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>

上述bean定义片段与以下代码片段完全相同(在运行时):

"..." />

<bean "client" "targetName" value="theTargetBean"/>
</bean>

第一种形式优于第二种形式,因为使用idref标签允许容器在部署时验证引用的命名bean实际存在。 在第二个变体中,不会对传递给clientbean的targetName属性的值执行验证。clientbean实际上被实例化时,才会发现错字(最可能是致命的结果)。 如果clientbean是prototypebean,,则此错误和生成的异常只能在容器部署后才能被发现。

注意

在4.0 beans xsd中,idref元素上的local属性不再受支持,因为它不再提供超过常规bean引用的值。 在升级到4.0模式时,只需将您现有的idref local引用更改为idref bean

一个常见的地方(至少在Spring 2.0之前的版本中),其中<idref />元素带来价值是在ProxyFactoryBean bean定义中配置AOP拦截器。 指定拦截器名称时使用<idref />元素可防止拼写错误。

引用其他bean(协作者)

ref元素是<property />定义元素中的最后一个元素。 在这里,您可以将bean的指定属性的值设置为对由容器管理的另一个bean(协作者)的引用。 引用的bean是其属性将被设置的bean的依赖关系,并且在设置属性之前根据需要初始化它。 (如果协作者是单例bean,则可以由容器初始化它。)所有引用最终都是引用另一个对象。 范围和验证取决于是否通过bean,local,或者parent属性.指定其他对象的id / name。

通过<ref />标签的bean属性指定目标bean是最通用的形式,允许创建对同一容器或父容器中任何bean的引用,而不管它们是否在同一个XML文件中。bean属性的值可能与目标bean的id属性相同,或者与目标bean的name属性中的值之一相同。

<ref bean="someBean"/>

通过父属性指定目标bean创建对当前容器的父容器中的bean的引用。 父属性的值可能与目标bean的id属性或目标bean的name属性中的值之一相同,目标bean必须位于当前bean的父容器中。 您使用此bean参考变体主要是在具有容器层次结构时,并且想要使用与父bean具有相同名称的代理将现有bean包装在父容器中。

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as 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 local参考引用改为ref bean

内部bean

<property /><constructor-arg />元素中的<bean />元素定义了一个所谓的内部bean。

"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或名称; 如果指定,容器不使用这样的值作为标识符。 容器还会忽略创建的scope标志:内部bean始终是匿名的,它们始终使用外部bean创建。 将内部bean注入到除了封闭bean之外的协作bean中,或者独立访问它们是不可能的。

作为一个案例,可以从自定义范围接收销毁回调,例如。 对于包含在单例bean中的请求显示的内部bean:内部bean实例的创建将与其包含的bean绑定,但是破坏回调允许它参与请求范围的生命周期。 这不是一个常见的情况; 内部bean通常只是分享它们的包含bean的范围。

集合

<list /><set /><map /><props />元素中,您可以分别设置Java集合类型列表,集合,映射和属性的属性和参数。

"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的键或值或设置值的值也可以是以下任何元素:

bean | ref | idref | list | set | map | props | value | null

集合的合并

Spring容器还支持集合的合并。 应用程序开发人员可以定义父样式的<map /><set /><props />元素,并且具有子样式<props />元素继承并覆盖父集合中的值。 也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。

关于合并的这个部分讨论了父子bean的机制。 不熟悉父和子bean定义的读者可能希望在继续之前阅读相关章节。

以下示例演示集合合并:

"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属性的<props />元素上使用merge = true属性。 当子bean由容器解析并实例化时,生成的实例具有一个adminEmails属性集合,该集合包含将该子adminEmails集合与父adminEmails集合合并的结果。

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

子属性集合的值集继承父项<props />中的所有属性元素,并且支持值的子级值覆盖父集合中的值。

此合并行为类似于<set />集合类型。 在<list />元素的具体情况下,维护与List集合类型相关联的语义,即有序的值集合的概念; 父项的值先于所有子列表的值。 在MapSetProperties集合类型的情况下,不存在排序。 因此,对于容器在内部使用的关联的Properties实现类型的集合类型,没有有效的语义。

集合合并的限制

您不能合并不同的集合类型(如Map和List),如果您尝试这样做,则会抛出适当的异常。 合并属性必须在较低的,继承的子定义上指定; 在父集合定义上指定合并属性是多余的,不会导致所需的合并。

强类型集合

通过在Java 5中引入泛型,可以使用强类型集合。 也就是说,可以声明一个Collection类型,使得它只能包含String元素(例如)。 如果您使用Spring将依赖性注入泛型的集合到一个bean中,那么您可以利用Spring的类型转换支持,以便将强类型的Collection实例的元素转换为适当的类型,然后才能添加到 集合。

Foo {

private Map<String,Float> accounts;

setAccounts(Map<String,Float> accounts) {
this.accounts = accounts;
}
}
"x.y.Foo">
<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>

foobean的accounts属性准备注入时,有关强类型Map <String,Float>的元素类型的泛型信息可通过反射获得。 因此,Spring的类型转换基础设施将各种值元素识别为Float类型,并将字符串值9.99,244)">2.75和3.99转换为实际的Float类型。

空和空字符串值

Spring将空参数作为空strings处理为属性等。以下基于XML的配置元数据片段将email属性设置为空string值(”“)。

<bean class="ExampleBean">
<property name="email" value=""/>
</bean>

上述示例相当于以下Java代码:

exampleBean.setEmail("")

<null />元素处理null值。 例如:

"email">
<null/>
</property>
</bean>

上述配置相当于以下Java代码:

exampleBean.setEmail(null)

p-namespace

p-namespace使您能够使用bean元素的属性,而不是嵌套的<property />元素,以描述您的属性值and/or协作bean。

Spring支持具有命名空间的可扩展配置格式,它们基于XML模式定义。 本章讨论的beans配置格式在XML Schema文档中定义。 但是,p命名空间没有在XSD文件中定义,只存在于Spring的核心。

以下示例显示了解决相同结果的两个XML片段:第一个使用标准XML格式,第二个使用p-namespace。

"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="foo@bar.com"/>
</bean>

<bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/>
</beans>

该示例显示了在bean定义中称为电子邮件的p命名空间中的属性。 这告诉Spring包含一个属性声明。 如前所述,p-namespace没有模式定义,因此您可以将属性的名称设置为属性名称。

下一个示例包括两个对另一个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
http://www.springframework.org/schema/beans/spring-beans.xsd"
>
<bean "john-classic" "com.example.Person">
<"name" value="John Doe"/>
<"spouse" ref="jane"/>
</bean>

<bean "john-modern"
"com.example.Person"
p:"John Doe"
p:spouse-"jane"/>

<bean "jane" "Jane Doe"/>
</bean>
</beans>

您可以看到,此示例不仅包括使用p-namespace的属性值,还使用特殊格式来声明属性引用。 而第一个bean定义使用<property name =“spouse”ref =“jane”/>来创建从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref="jane"作为属性来执行 完全一样的事情。 在这种情况下,配偶是属性名称,而-ref部分表示这不是一个直的值,而是引用另一个bean。

注意

p-namespace不如标准XML格式那么灵活。 例如,声明属性引用的格式与以Ref结尾的属性冲突,而标准XML格式则不会。 我们建议您仔细选择您的方法,并将其传达给您的团队成员,以避免同时生成使用所有三种方法的XML文档。

c-namespace

类似于称为“具有p:namespace的XML快捷方式”一节,Spring 3.1中新引入的c命名空间允许使用内联属性来配置构造函数参数,而不是使用嵌套constructor-arg元素。

我们来回顾一下名为“基于构造器的依赖注入”一节中与c:namespace的例子:

"http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
>
<bean id="x.y.Bar"/>

<bean id="x.y.Baz"/>

<!-- traditional declaration -->
<bean id="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>

<!-- c-namespace declaration -->
<bean id="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

c:namespace使用与p:one相同的约定(用于bean引用的尾部-ref)用于通过名称来设置构造函数参数。 同样,它也需要声明,即使它没有在XSD模式中定义(但它存在于Spring内核中)。

对于构造函数参数名称不可用的罕见情况(通常如果字节码在没有调试信息的情况下编译),可以对参数索引使用回退:

<!-- c-namespace index declaration -->
<bean id="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

注意

由于XML语法,索引符号需要前导_的存在,因为XML属性名称不能以数字开头(即使某些IDE允许)。

在实践中,构造函数解析机制在匹配参数方面非常有效,除非真的需要,我们建议您使用名称符号通过配置。

复合属性名称

当您设置bean属性时,可以使用复合或嵌套属性名称,只要除了最终属性名称之外的路径的所有组件都不为null。 考虑下面的bean定义。

<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>

foobean有一个fred属性,它具有一个bob属性,它具有sammy属性,并且最终sammy属性被设置为值123.为了这个工作,foofred属性和bob属性 在构造bean之后,fred不能为null,或抛出NullPointerException

7.4.3 使用依赖

如果一个bean是另一个的依赖,通常意味着一个bean被设置为另一个bean的属性。 通常,您可以使用基于XML的配置元数据中的<ref />元素来完成此任务。 但是,有时bean之间的依赖关系较不直接; 例如,需要触发类中的静态初始化器,例如数据库驱动程序注册。 在使用此元素的bean初始化之前,depends-on属性可以显式强制一个或多个bean进行初始化。 以下示例使用depends-on属性来表示对单个bean的依赖关系:

"beanOne" class="ExampleBean" depends-on="manager"/>

<bean id="manager" class="ManagerBean" />

要表示对多个bean的依赖关系,请使用逗号,空格和分号作为有效分隔符提供一个bean名称列表作为depends-on属性的值:

"manager,accountDao">
<property name="manager" ref="manager" />
</bean>

<bean id="ManagerBean" />

<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

注意

bean定义中的depend-on属性可以指定初始化时间依赖关系,在单例bean的情况下,可以指定相应的销毁时间依赖关系。 在给定的bean本身被破坏之前,定义与给定bean的depends-on关系的依赖性对象首先被销毁。 因此depends-on也可以控制关机顺序。

7.4.4 延迟初始化bean

默认情况下,ApplicationContext实现在初始化过程中急切创建和配置所有单例bean。 一般来说,这种预实例化是可取的,因为配置或周围环境中的错误被立即发现,而不是几个小时甚至几天。 当这种行为是不可取的时,您可以通过将bean定义标记为延迟初始化来防止单例bean的预实例化。 一个lazy初始化的bean告诉IoC容器在首次请求时创建一个bean实例,而不是在启动时。

在XML中,此行为由元素上的lazy-init属性控制; 例如:

"lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

当上述配置被ApplicationContext使用时,当ApplicationContext启动时,名为lazy的bean不会被预先实例化,而not.lazybean则是急需预先实例化的。

然而,当一个惰性初始化的bean是不被延迟初始化的单例bean的依赖项时,ApplicationContext会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖性。 lazy初始化的bean被注入到其他不是惰性初始化的单例bean中。

您还可以通过使用<beans />元素上的default-lazy-init属性来控制容器级别的延迟初始化; 例如:

<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>

7.4.5 自动装配的合作者

Spring容器可以自动关联协作bean。 您可以通过检查ApplicationContext的内容来允许Spring自动解析您的bean的协作者(其他bean)。 自动装配具有以下优点:

  • 自动装配可以显着减少指定属性或构造函数参数的需要。 (其他机制,如本章其他地方讨论的bean模板在这方面也很有价值。)

  • 自动装配可以随着对象的发展而更新配置。 例如,如果您需要向类添加依赖关系,则可以自动满足该依赖关系,而无需修改配置。 因此,自动布线在开发过程中特别有用,而在代码库变得更加稳定时,无需切换到显式布线。

当使用基于XML的配置元数据10时,您可以使用元素的autowire属性为bean定义指定autowire模式。 自动连线功能有四种模式。 您可以指定每个bean的自动布线,因此可以选择要自动连线的。

Mode Explanation
no (默认)无自动装配。 Bean引用必须通过ref元素定义。 不建议更改默认设置用于较大的部署,因为明确指定协作者可以更好地控制和清晰。 在某种程度上,它记录了系统的结构。
byName 按属性名称自动装配。 Spring会找到一个与需要自动装配的属性名称相同的bean。 例如,如果bean定义按名称设置为autowire,并且它包含一个master属性(即它具有一个setMaster(..)方法),则Spring会查找一个名为master的bean定义,并使用它来设置 属性。
byType 如果容器中存在属性类型的一个bean,则允许属性自动装配。 如果存在多个,则会抛出一个致命异常,这表示您可能不会对该bean使用byType自动连线。 如果没有匹配的豆,没有任何反应; 该属性未设置。
constructor 类似于byType,但适用于构造函数参数。 如果在容器中没有一个构造函数参数类型的bean,则会引起致命错误。

使用byType或构造函数自动装配模式,可以对数组和类型集合进行装配。 在这种情况下,提供符合预期类型的容器内的所有自动装配候选者以满足依赖性。 如果预期的键类型为String,则可以自动连接强类型的地图。 自动装配的map值将包含与预期类型相匹配的所有bean实例,map key值将包含相应的bean名称。

您可以将自动装配行为与依赖关系检查相结合,这是自动连线完成后执行的。

自动装配的局限性和缺点

当项目一贯使用时,自动装配效果最好。 如果一般不使用自动装配,开发人员可能会混淆使用它来连接一个或两个bean定义。

考虑自动装配的局限性和缺点:

  • ·属性·和·构造器参数·设置中的显式依赖性总是覆盖自动装配。您不能自动连接所谓的简单属性,例如基本类型字符串(以及这些简单属性的数组)。 这个限制是按设计。

  • 自动装配不如显式装配精确。 尽管如上表所示,虽然Spring很小心避免在可能出现意想不到的结果的歧义的情况下进行猜测,但是您的Spring管理对象之间的关系不再被明确记录。

  • 对于可能从Spring容器生成文档的工具可能无法使用装配信息。

容器中的多个bean定义可能与由setter方法指定的类型或构造函数参数匹配以进行自动连线。 对于数组,集合或地图,这不一定是问题。 然而,对于期望单个值的依赖性,这种模糊性不是任意解决的。 如果没有唯一的bean定义可用,则抛出异常。

在后一种情况下,您有几个选项:

  • 放弃自动装配有利于明确布线。

  • 通过将其autowire-candidate属性设置为false,避免自动连接bean定义,如下一节所述。

  • 通过将其<bean />元素的主属性设置为true,将单个bean定义指定为主要候选。

  • 使用基于注释的配置实现更细粒度的控制,如第7.9节“基于注释的容器配置”所述。

从自动装配中排除一个bean

在每个bean的基础上,您可以将bean从自动装配中排除。 在Spring的XML格式中,将<bean />元素的false; 容器使特定的bean定义不可用于自动布线基础架构(包括注释样式配置,例如@Autowired)。

注意

autowire-candidate属性设计为仅影响基于类型的自动装配。 它不会影响名称的显式引用,即使指定的bean未标记为自动装配候选,也将得到解决。 因此,如果名称匹配,则通过名称自动装配将会注入一个bean。

您还可以基于与bean名称的模式匹配来限制自动应答。 toplevel<beans />元素在其default-autowire-candidates属性中接受一个或多个模式。 例如,要将autowire候选状态限制为名称以Repository结尾的任何bean,请提供Repository的值。 要提供多种模式,请以逗号分隔的列表定义它们。 bean定义autowire-candidate属性的显式值为truefalse始终优先,对于此类bean,模式匹配规则不适用。

这些技术对于您不想通过自动连线注入其他bean的bean很有用。 这并不意味着排除的bean本身不能使用自动布线来配置。 相反,bean本身不是自动连线其他bean的候选者。

7.4.6 方法注入

在大多数应用场景中,容器中的大多数bean都是单例。 当一个单例bean需要与另一个singleton bean协作时,或者一个非singleton bean需要与另一个非singleton bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。 当bean生命周期不同时,会出现问题。 假设单例bean A需要使用非单例(原型)bean B,也许在A上的每个方法调用上。容器只创建单例bean A一次,因此只能获得一个设置属性的机会。 每次需要时,容器不能向bean A提供一个新的bean B实例。

解决方案是放弃一些控制反转。 您可以通过实现ApplicationContextAware接口来使bean A知道容器,并且通过对容器的getBean(“B”)调用在每次bean A需要时询问(通常是新的)B B实例。 以下是这种方法的一个例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

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);
}

setApplicationContext(
ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}

上述是不可取的,因为业务代码知道并耦合到Spring框架。 方法注入是Spring IoC容器的一个先进的功能,可以以干净的方式处理这种用例。

可以在此博客条目中阅读更多关于方法注入的动机。
<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注释声明一个查找方法:

abstract CommandManager {
(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command ();
}

或者,更为惯用的是,您可能依赖于目标bean针对声明的返回类型的lookup方法进行解决:

(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}

@Lookup
abstract MyCommand ();
}

请注意,您通常会使用具体的stub实现声明这种带注释的查找方法,以使它们与Spring的组件扫描规则兼容,其中抽象类默认情况下被忽略。 此限制不适用于明确注册或明确导入的bean类的情况。

另一种访问不同范围的目标bean的方法是ObjectFactory / Provider注入点。 查看“Scoped beans as dependencies”一节。

感兴趣的读者还可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)来使用。

任意方法更换

方法注入比查找方法注入的一种不太有用的形式是使用另一种方法实现来替代托管bean中的任意方法。 用户可以安全地跳过本节的其余部分,直到实际需要该功能。

使用基于XML的配置元数据,可以使用replacement-method元素将现有的方法实现替换为已部署的bean。 考虑下面的类,使用一个方法computeValue,我们要覆盖:

MyValueCalculator {
String computeValue(String input) {
// some real code...
}
// some other methods...
}

实现org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法定义。

/**
* meant to be used to override
the existing computeValue(String)
* implementation in MyValueCalculator
*/

public class ReplacementComputeValue 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定义如下所示:

"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"/>

您可以在<replaced-method />中使用一个或多个包含的<arg-type />元素
元素来指示被覆盖的方法的方法签名。 只有当方法重载并且类中存在多个变体时,参数的签名才是必需的。 为方便起见,参数的类型字符串可能是完全限定类型名称的子字符串。 例如,以下全部匹配java.lang.String

java.lang.String
String
Str

由于参数的数量通常足以区分每个可能的选择,所以此快捷方式可以节省大量的打字,只需键入与参数类型匹配的最短字符串即可。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读