Bootstrap

Spring——依赖项

典型的企业应用程序不包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序也有一些对象一起工作来呈现最终用户所认为的连贯的应用程序。下一节将解释如何从定义多个独立的 bean 定义转变为一个完全实现的应用程序,及在该应用程序中的对象协作。

依赖注入

依赖注入 (DI) 是一个过程,对象仅通过构造函数参数、工厂方法的参数或对象实例构造后设置的属性来定义其依赖项(即与它们一起工作的其他对象)。从工厂方法返回。
然后,容器在创建 bean 时注入这些依赖项。这个过程从根本上来说是 bean 本身的逆过程(因此得名“控制反转”),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。

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

DI 存在两种主要变体:

基于构造函数的依赖注入基于 Setter 的依赖注入

基于构造函数的依赖注入


基于构造函数的 DI 是通过容器 调用带有多个参数的构造函数来完成的,每个参数代表一个依赖项
与调用具有特定参数的static工厂方法来构造 bean 几乎是等效的,并且本讨论以类似方式对待构造函数和static工厂方法的参数。以下示例显示了一个只能通过构造函数注入进行依赖注入的类:

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 时,类型是已知的,并且可以发生匹配(如前面示例的情况)。
当使用简单类型时,例如 <value>true</value>,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


在上述场景中,如果您使用属性显式指定构造函数参数的类型,则容器可以使用简单类型的类型匹配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 开始的。

构造函数参数名称——name
还可以使用构造函数参数名称来消除值歧义,如以下示例所示:

<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 的 DI 是通过容器在 调用无参构造函数或无参static工厂方法 来实例化您的 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。
您可以以 a 的形式配置依赖项BeanDefinition,将其与实例结合使用PropertyEditor,将属性从一种格式转换为另一种格式。
然而,大多数 Spring 用户并不直接(即以编程方式)使用这些类,而是使用 XMLbean 定义、带注释的组件(即用@Component、 @Controller等注释的类)或@Bean基于 Java 的@Configuration类中的方法。然后,这些源在内部转换为实例BeanDefinition并用于加载整个 Spring IoC 容器实例

基于构造函数还是基于 setter 的 DI?
由于您可以混合使用基于构造函数和基于 setter 的 DI,因此最好的经验法则是使用构造函数来实现强制依赖项,并使用 setter 方法或配置方法来实现可选依赖项。
请注意,在 setter 方法上使用@Autowired 注释可以使该属性成为必需的依赖项;
然而,带有编程验证参数的构造函数注入是更可取的。
Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null。此外,构造函数注入的组件始终以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码味道,这意味着该类可能有太多的职责,应该进行重构以更好地解决适当的关注点分离问题。
Setter 注入主要应该仅用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的所有地方执行非空检查。setter 注入的好处之一是 setter 方法使该类的对象可以在以后重新配置或重新注入。因此,通过JMX MBean进行管理是 setter 注入的一个引人注目的用例。
使用对特定类最有意义的 DI 样式。有时,在处理您没有源代码的第三方类时,系统会为您做出选择。例如,如果第三方类不公开任何 setter 方法,则构造函数注入可能是 DI 唯一可用的形式。

依赖解析过程

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

  • ApplicationContext使用 所有 描述bean 的配置元数据创建并初始化。配置元数据可以通过 XML、Java 代码或注释来指定。
  • 对于每个 bean,其依赖项以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。这些依赖关系是在实际创建 bean 时提供给 bean 的。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个 bean 的引用。
  • 作为值的每个属性或构造函数参数都会从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如int、 long、String、boolean等。

Spring 容器在创建容器时验证每个 bean 的配置。但是,直到实际创建 bean 后,才会设置 bean 属性本身。
单例范围并设置为预实例化(默认)的 Bean 是在创建容器时创建的。范围在Bean Scopes中定义。
否则,仅当请求时才创建 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 的引用和循环依赖项。 当 bean 实际创建时,Spring 会尽可能晚地设置属性并解决依赖关系。 这意味着,当您请求一个对象时,如果创建该对象或其依赖项之一出现问题,则已正确加载的 Spring 容器稍后会生成异常 。
例如,bean 由于缺失或无效而引发异常。某些配置问题的可见性可能会延迟,这就是ApplicationContext默认情况下实现预实例化单例 bean 的原因在实际需要这些 bean 之前创建这些 bean 需要付出一些前期时间和内存的代价,您会在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"/>

以下示例显示了相应的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 调用static工厂方法来返回对象的实例:

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


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

依赖关系和配置详细信息

您可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用或内联定义的值。

直接值(原语、字符串等)

value元素的属性将<property/>属性或构造函数参数指定为人类可读的字符串表示形式。Spring 的 转换服务用于将这些值从 a 转换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或Spring Tools for Eclipse )。强烈推荐此类 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机制将元素内的文本转换<value/>为 实例。这是一个很好的快捷方式,也是 Spring 团队支持使用嵌套元素而不是属性样式的少数几个地方之一。java.util.PropertiesPropertyEditor<value/>value

idref标签

元素idref只是一种防错方法,用于将id容器中另一个 bean 的(字符串值 - 而不是引用)传递给或 元素。以下示例展示了如何使用它:

<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 是否确实存在。
targetName在第二种变体中,不对传递给 bean属性的值执行任何验证client。client仅当bean 实际实例化时才会发现拼写错误(很可能导致致命结果) 。如果该client bean 是原型bean,则这种拼写错误和由此产生的异常可能只有在容器部署很久之后才会被发现。

References to Other Beans (对其他 Bean的引用)

Inner Beans(内部bean)

元素<bean/>内的元素定义一个内部 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 或名称。如果指定,容器不会使用此类值作为标识符。容器还会忽略scope创建时的标志,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建。无法独立访问内部 Bean 或将它们注入到协作 Bean 中(除了封闭 Bean 中)。

Collections(集合)

<list/>, <set/>, <map/><props/>元素分别设置 Java的Collection类型List、set、map、和Properties的属性和参数。以下示例展示了如何使用它们:CollectionListSetMapProperties

<bean id="moreComplexObject" class="example.ComplexObject">
	<!-- results in a setAdminEmails(java.util.Properties) call -->
	<property name="adminEmails">
		<props>
			<prop key="administrator">[email protected]</prop>
			<prop key="support">[email protected]</prop>
			<prop key="development">[email protected]</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>

映射键或值的值,或者集合值,也可以是以下任何元素:

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

Using depends-on(使用depends-on)

如果一个 bean 是另一个 bean 的依赖项,则通常意味着一个 bean 被设置为另一个 bean 的属性。
通常,您可以使用基于 XML 的配置元数据中的<ref/> 元素来完成此操作。然而, 有时 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 的情况下,还可以指定相应的销毁时依赖关系。定义depends-on与给定 bean 的关系的依赖 bean 在给定 bean 本身被销毁之前首先被销毁。这样,depends-on还可以控制关​​机顺序。

Lazy-initialized Beans(延迟初始化 Bean)


默认情况下,ApplicationContext实现会在初始化过程中急切地创建和配置所有 单例bean。

一般来说,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是数小时甚至数天后。
如果不需要这种行为,您可以通过将 Bean 定义标记为延迟初始化来防止预实例化单例 Bean。延迟初始化的 bean 告诉 IoC 容器在第一次请求时而不是在启动时创建一个 bean 实例。

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

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

当前面的配置被 ApplicationContext消费时,lazybean 在启动时不会被ApplicationContext急切地预实例化,而非lazyBean 会被急切地预实例化。

但是,当延迟初始化 bean 是未延迟初始化的单例 bean 的依赖项时,ApplicationContext会在启动时创建延迟初始化 bean,因为它必须满足单例的依赖项。延迟初始化的 bean 被注入到其他未延迟初始化的单例 bean 中。

您还可以使用 元素default-lazy-init上的属性在容器级别控制延迟初始化<beans/>,如以下示例所示:

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

Autowiring Collaborators (自动装配合作者)

Spring 容器可以自动装配协作 bean 之间的关系。您可以让 Spring 通过 ApplicationContext 检查 .xml 文件的内容来自动为您的 bean 解析协作者(其他 bean)。

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

自动装配具有以下优点:

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

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

当使用基于 XML 的配置元数据(请参阅依赖注入autowire)时,您可以使用元素的属性 为 bean 定义指定自动装配模式。
自动装配功能有四种模式。您可以为每个 bean 指定自动装配,从而可以选择要自动装配的 bean。
下表描述了四种自动装配模式:

使用byType自动constructor装配模式,您可以连接数组和类型化集合。在这种情况下,将提供容器内与预期类型匹配的所有自动装配候选者以满足依赖性。Map如果预期的键类型是 ,您可以自动装配强类型实例String。自动装配Map 实例的值由与预期类型匹配的所有 bean 实例组成, Map实例的键包含相应的 bean 名称。

自动装配的局限性和缺点

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

  • property和设置中的显式依赖项constructor-arg始终会覆盖自动装配。您无法自动装配简单属性,例如基元、 Strings和Classes(以及此类简单属性的数组)。此限制是设计使然。

  • 自动连线不如显式连线精确。尽管如此,正如前面的表中所指出的,Spring 会小心地避免猜测,以防出现可能产生意外结果的歧义。Spring 管理的对象之间的关系不再明确记录。

  • 接线信息可能无法用于从 Spring 容器生成文档的工具。

  • 容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组、集合或 Map实例来说,这不一定是问题。但是,对于期望单个值的依赖项,这种歧义不是任意解决的。如果没有可用的唯一 bean 定义,则会引发异常

在后一种情况下,您有多种选择:

  • 放弃自动装配,转而采用显式装配。

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

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

  • 通过基于注释的配置实现更细粒度的控制,如基于注释的容器配置中所述。

从自动装配中排除 Bean

在每个 bean 的基础上,您可以从自动装配中排除 bean。在 Spring 的 XML 格式中,将元素<bean/>autowire-candidate 属性设置为false。容器使特定bean 的定义对自动装配基础设施不可用(包括注释样式配置,例如@Autowired)。

该autowire-candidate属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会得到解析。因此,如果名称匹配,按名称自动装配仍然会注入一个 bean。

您还可以根据与 bean 名称的模式匹配来限制自动装配候选者。顶级元素接受其属性内的一种或多种模式 default-autowire-candidates。例如,要将自动装配候选状态限制为名称以 结尾的任何 bean Repository,请提供值*Repository。要提供多种模式,请在逗号分隔的列表中定义它们。bean 定义的属性的显式值 true或该属性的显式值始终优先。对于此类 Bean,模式匹配规则不适用。falseautowire-candidate

这些技术对于您不想通过自动装配注入其他 Bean 的 Bean 非常有用。这并不意味着排除的 bean 本身不能使用自动装配进行配置。相反,bean 本身并不是自动装配其他 bean 的候选者。

方法注入

在大多数应用场景中,容器中的大部分bean都是单例的。常见的controller、service、componment、configration等注解修饰的默认都是单例。
当一个单例 bean 需要与另一个单例 bean 协作或一个非单例 bean 需要与另一个非单例 bean 协作时,通常可以通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。
当 bean 生命周期不同时就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,可能是在 A 上的每次方法调用上。容器仅创建单例 bean A 一次,因此只有一次设置属性的机会。每次需要 bean B 时,容器无法为 bean A 提供新的 bean B 实例。

解决方案是放弃一些控制反转。您可以通过实现接口来使 bean A 知道容器ApplicationContextAware,并在每次 bean A 需要时调用getBean("B")容器来请求(通常是新的)bean 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;

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 IoC 容器的一项高级功能,可让您干净地处理此用例。

Lookup Method Injection (查找方法注入)

查找方法注入是 容器重写 容器管理的 Bean 上的方法并返回容器中另一个命名 Bean 的查找结果的能力。
查找通常涉及prototype bean,如上一节中描述的场景。Spring框架通过使用CGLIB库的字节码生成动态生成覆盖该方法的子类来实现此方法注入。

  • 为了使这种动态子类化工作,Spring bean 容器子类化的类不能是final,并且要重写的方法也不能是final 。

  • 对具有方法的类进行单元测试abstract需要您自己对该类进行子类化并提供该abstract方法的存根实现。

  • 组件扫描也需要具体的方法,这需要选取具体的类。

  • 另一个关键限制是查找方法不能与工厂方法一起使用,特别是不能与@Bean配置类中的方法一起使用,因为在这种情况下,容器不负责创建实例,因此无法在其上创建运行时生成的子类。

对于CommandManager前面的代码片段中的类,Spring 容器动态地覆盖该方法的实现createCommand() 。该类CommandManager没有任何 Spring 依赖项,如修改后的示例所示:

package fiona.apple;

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

如果该方法是abstract,则动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:

<!-- 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 实例时commandManager都会调用自己的createCommand()方法。如果确实需要,则必须小心地将 bean myCommand部署为原型。如果它是单例, 则每次都会返回相同的 myCommand 实例。

或者,在基于注释的组件模型中,您可以通过注释声明查找方法@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) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

任意方法替换

与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一种方法实现替换托管 bean 中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。

通过基于 XML 的配置元数据,您可以使用该replaced-method元素将已部署 bean 的现有方法实现替换为另一个方法实现。考虑下面的类,它有一个computeValue我们要重写的方法:

public class MyValueCalculator {

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

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

java.lang.String
String
Str

由于参数的数量通常足以区分每个可能的选择,因此此快捷方式可以让您仅键入与参数类型匹配的最短字符串,从而节省大量打字时间。

;