对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

方法注入

在大多数应用场景中,容器中的大部分 bean 都是 单例(singleton)。当一个单例 bean 需要与另一个单例 bean 协作,或者一个非单例 bean 需要与另一个非单例 bean 协作时,通常的做法是将其中一个 bean 定义为另一个 bean 的属性来处理这种依赖关系。然而,当 bean 的生命周期不同时,就会出现问题。假设单例 bean A 需要在每次调用其方法时使用一个非单例(原型,prototype)bean B。容器只会创建一次单例 bean A,因此只有一次机会设置其属性。容器无法在每次需要时都为 bean A 提供一个新的 bean B 实例。spring-doc.cadn.net.cn

一种解决方案是放弃部分控制反转。您可以通过实现 ApplicationContextAware 接口,使 Bean A 感知容器,并通过向容器发起 getBean("B") 调用,在 Bean A 每次需要时请求(通常是新的)Bean B 实例。以下示例展示了这种方法:spring-doc.cadn.net.cn

package fiona.apple;

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

/**
 * A class that uses a stateful Command-style class to perform
 * some processing.
 */
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;
	}
}
package fiona.apple

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

// A class that uses a stateful Command-style class to perform
// some processing.
class CommandManager : ApplicationContextAware {

	private lateinit var applicationContext: ApplicationContext

	fun process(commandState: Map<*, *>): Any {
		// grab a new instance of the appropriate Command
		val command = createCommand()
		// set the state on the (hopefully brand new) Command instance
		command.state = commandState
		return command.execute()
	}

	// notice the Spring API dependency!
	protected fun createCommand() =
			applicationContext.getBean("command", Command::class.java)

	override fun setApplicationContext(applicationContext: ApplicationContext) {
		this.applicationContext = applicationContext
	}
}

上述做法并不理想,因为业务代码感知到了 Spring 框架并与之耦合。方法注入(Method Injection)是 Spring IoC 容器的一项较为高级的特性,可让你干净利落地处理此类用例。spring-doc.cadn.net.cn

您可以在这篇博客文章中了解更多关于方法注入的动机。spring-doc.cadn.net.cn

查找方法注入

查找方法注入(Lookup method injection)是指容器能够重写容器所管理的 bean 上的方法,并返回容器中另一个已命名 bean 的查找结果。这种查找通常涉及原型(prototype)作用域的 bean,如前一节所述场景所示。Spring 框架通过使用 CGLIB 库进行字节码生成,动态创建一个子类来重写该方法,从而实现这种方法注入。spring-doc.cadn.net.cn

  • 要使这种动态子类化生效,Spring Bean 容器所生成子类的类不能是 final 的,且需要被重写的方法也不能是 final 的。spring-doc.cadn.net.cn

  • 对包含 abstract 方法的类进行单元测试时,需要你自己创建该类的子类,并为该 abstract 方法提供一个桩(stub)实现。spring-doc.cadn.net.cn

  • 具体方法对于组件扫描也是必需的,因为组件扫描需要具体的类才能被识别。spring-doc.cadn.net.cn

  • 另一个关键限制是,查找方法无法与工厂方法一起使用,尤其不能与配置类中的 @Bean 方法一起使用,因为在那种情况下,容器并不负责创建实例,因此无法在运行时动态生成子类。spring-doc.cadn.net.cn

在前面代码片段中的 CommandManager 类中, Spring 容器会动态地重写 createCommand() 方法的实现。正如重构后的示例所示,CommandManager 类没有任何 Spring 依赖:spring-doc.cadn.net.cn

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();
}
package fiona.apple

// no more Spring imports!

abstract class CommandManager {

	fun process(commandState: Any): Any {
		// grab a new instance of the appropriate Command interface
		val command = createCommand()
		// set the state on the (hopefully brand new) Command instance
		command.state = commandState
		return command.execute()
	}

	// okay... but where is the implementation of this method?
	protected abstract fun createCommand(): Command
}

在包含要注入方法的客户端类中(本例中为 CommandManager),该要注入的方法需要具有如下形式的签名:spring-doc.cadn.net.cn

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果该方法是abstract的,则动态生成的子类会实现该方法。 否则,动态生成的子类将重写原始类中定义的具体方法。请考虑以下示例:spring-doc.cadn.net.cn

<!-- 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 在每次需要一个新的 createCommand() bean 实例时,都会调用其自身的 myCommand 方法。如果确实需要的是新实例,你必须小心地将 myCommand bean 部署为原型(prototype)。如果它是一个单例(singleton),则每次都会返回同一个 myCommand bean 实例。spring-doc.cadn.net.cn

或者,在基于注解的组件模型中,您可以通过 @Lookup 注解声明一个查找方法,如下例所示:spring-doc.cadn.net.cn

public abstract class CommandManager {

	public Object process(Object commandState) {
		Command command = createCommand();
		command.setState(commandState);
		return command.execute();
	}

	@Lookup("myCommand")
	protected abstract Command createCommand();
}
abstract class CommandManager {

	fun process(commandState: Any): Any {
		val command = createCommand()
		command.state = commandState
		return command.execute()
	}

	@Lookup("myCommand")
	protected abstract fun createCommand(): Command
}

或者,更符合习惯的做法是,你可以依赖目标 bean 根据查找方法声明的返回类型进行解析:spring-doc.cadn.net.cn

public abstract class CommandManager {

	public Object process(Object commandState) {
		Command command = createCommand();
		command.setState(commandState);
		return command.execute();
	}

	@Lookup
	protected abstract Command createCommand();
}
abstract class CommandManager {

	fun process(commandState: Any): Any {
		val command = createCommand()
		command.state = commandState
		return command.execute()
	}

	@Lookup
	protected abstract fun createCommand(): Command
}

请注意,通常应为这类带注解的查找方法声明一个具体的存根(stub)实现,以确保它们与 Spring 的组件扫描规则兼容,因为默认情况下抽象类会被忽略。此限制不适用于显式注册或显式导入的 Bean 类。spring-doc.cadn.net.cn

访问不同作用域目标 Bean 的另一种方式是使用 ObjectFactory/Provider 注入点。参见作为依赖项的作用域 Beanspring-doc.cadn.net.cn

您可能还会发现 ServiceLocatorFactoryBean(位于 org.springframework.beans.factory.config 包中)很有用。spring-doc.cadn.net.cn

任意方法替换

与查找方法注入相比,另一种用处较小的方法注入形式是:能够用另一个方法实现来替换托管 Bean 中的任意方法。在你真正需要此功能之前,可以安全地跳过本节的其余内容。spring-doc.cadn.net.cn

使用基于 XML 的配置元数据,您可以使用 replaced-method 元素为已部署的 Bean 将现有方法的实现替换为另一个实现。请考虑以下类,该类包含一个名为 computeValue 的方法,我们希望重写该方法:spring-doc.cadn.net.cn

public class MyValueCalculator {

	public String computeValue(String input) {
		// some real code...
	}

	// some other methods...
}
class MyValueCalculator {

	fun computeValue(input: String): String {
		// some real code...
	}

	// some other methods...
}

一个实现 org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如下例所示:spring-doc.cadn.net.cn

/**
 * 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 ...;
	}
}
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
class ReplacementComputeValue : MethodReplacer {

	override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
		// get the input value, work with it, and return a computed result
		val input = args[0] as String;
		...
		return ...;
	}
}

用于部署原始类并指定方法重写的 bean 定义将类似于以下示例:spring-doc.cadn.net.cn

<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 匹配:spring-doc.cadn.net.cn

java.lang.String
String
Str

由于参数的数量通常足以区分每个可能的选项,因此这个快捷方式可以节省大量输入,让你只需键入匹配参数类型的最短字符串即可。spring-doc.cadn.net.cn