Method Injection

在大多数应用程序场景中,容器中的大多数 bean 都为 singletons 。当单例 bean 需要与其他单例 bean 协作,或者非单例 bean 需要与其他非单例 bean 协作时,通常通过将一个 bean 定义为另一个 bean 的属性来处理该依赖项。当 bean 生命周期不同时,就会出现问题。假设单例 bean A 需要使用非单例(原型)bean B,也许在对 A 的每次方法调用中使用它。容器仅创建一次单例 bean A,因此只有一次设置属性的机会。容器无法在每次需要时都为 bean A 提供 bean B 的新实例。

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

一个解决方案是放弃某些控制反转。您可以通过 make bean A aware of the container,实现 ApplicationContextAware 接口,并在 making a getBean("B") call to the container 中每次需要 bean A 时请求(通常是新的)bean B 实例。以下示例演示了此方法:

A solution is to forego some inversion of control. You can make bean A aware of the container by implementing the ApplicationContextAware interface, and by making a getBean("B") call to the container ask for (a typically new) bean B instance every time bean A needs it. The following example shows this approach:

  • Java

  • Kotlin

// 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;
	}
}
// 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 框架并与其耦合。Spring IoC 容器的一种比较高级的功能——方法注入,让你可以干净地处理此用例。

The preceding is not desirable, because the business code is aware of and coupled to the Spring Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, lets you handle this use case cleanly.

你可以在https://spring.io/blog/2004/08/06/method-injection/[此博客文章]中阅读有关方法注入动机的更多内容。

You can read more about the motivation for Method Injection in this blog entry.

Lookup Method Injection

查找方法注入是指容器覆盖容器管理的 bean 上的方法,并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及原型 bean,如同 the preceding section中描述的场景。Spring 框架通过使用 CGLIB 库进行字节码生成来实现这种方法注入,该生成会动态生成一个覆盖该方法的子类。

Lookup method injection is the ability of the container to override methods on container-managed beans and return the lookup result for another named bean in the container. The lookup typically involves a prototype bean, as in the scenario described in the preceding section. The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method.

  • For this dynamic subclassing to work, the class that the Spring bean container subclasses cannot be final, and the method to be overridden cannot be final, either.

  • Unit-testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method.

  • Concrete methods are also necessary for component scanning, which requires concrete classes to pick up.

  • A further key limitation is that lookup methods do not work with factory methods and in particular not with @Bean methods in configuration classes, since, in that case, the container is not in charge of creating the instance and therefore cannot create a runtime-generated subclass on the fly.

在上一段代码片段中的 CommandManager 类的情况下,Spring 容器动态覆盖了 createCommand() 方法的实现。正如重新编写的示例所示,CommandManager 类没有任何 Spring 依赖关系:

In the case of the CommandManager class in the previous code snippet, the Spring container dynamically overrides the implementation of the createCommand() method. The CommandManager class does not have any Spring dependencies, as the reworked example shows:

  • Java

  • Kotlin

// 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();
}
// 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)的客户机类中,要注入的方法需要具有以下形式的签名:

In the client class that contains the method to be injected (the CommandManager in this case), the method to be injected requires a signature of the following form:

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

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

If the method is abstract, the dynamically-generated subclass implements the method. Otherwise, the dynamically-generated subclass overrides the concrete method defined in the original class. Consider the following example:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
	<!-- inject dependencies here as required -->
</bean>

<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
	<lookup-method name="createCommand" bean="myCommand"/>
</bean>

标识为 commandManager 的 bean 每当它需要 myCommand bean 的新实例时,它都会调用它自己的 createCommand() 方法。如果你实际上需要这样,就必须小心地将 myCommand bean 部署为原型。如果它是一个 singleton,则每次都会返回 myCommand bean 的同一个实例。

The bean identified as commandManager calls its own createCommand() method whenever it needs a new instance of the myCommand bean. You must be careful to deploy the myCommand bean as a prototype if that is actually what is needed. If it is a singleton, the same instance of the myCommand bean is returned each time.

或者,在基于注释的组件模型内,可以通过 @Lookup 注释声明查找方法,如下例所示:

Alternatively, within the annotation-based component model, you can declare a lookup method through the @Lookup annotation, as the following example shows:

  • Java

  • Kotlin

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:

Or, more idiomatically, you can rely on the target bean getting resolved against the declared return type of the lookup method:

  • Java

  • Kotlin

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
}

请注意,你通常应该使用具体的存根实现来声明此类带注释的查找方法,以便其与 Spring 的组件扫描规则兼容,其中默认会忽略抽象类。此限制不适用于显式注册或显式导入的 Bean 类。

Note that you should typically declare such annotated lookup methods with a concrete stub implementation, in order for them to be compatible with Spring’s component scanning rules where abstract classes get ignored by default. This limitation does not apply to explicitly registered or explicitly imported bean classes.

访问不同作用域目标 bean 的另一种方法是 ObjectFactory/ Provider 注入点。参见 Scoped Beans as Dependencies

Another way of accessing differently scoped target beans is an ObjectFactory/ Provider injection point. See Scoped Beans as Dependencies.

您还可能发现 ServiceLocatorFactoryBean(在`org.springframework.beans.factory.config` 包中)很有用。

You may also find the ServiceLocatorFactoryBean (in the org.springframework.beans.factory.config package) to be useful.

Arbitrary Method Replacement

比查询方法注入功能弱一些的方法注入形式是用另一个方法实现替换受管 Bean 中的任意方法。如果您现在不需要此功能,可以安全地跳过本节的剩余部分。

A less useful form of method injection than lookup method injection is the ability to replace arbitrary methods in a managed bean with another method implementation. You can safely skip the rest of this section until you actually need this functionality.

使用基于 XML 的配置元数据,您可以使用 replaced-method 元素用另一个元素替换现有方法实现,以获取已部署的 Bean。请考虑以下类,其中有一个名为 computeValue 的方法,我们想重写此方法:

With XML-based configuration metadata, you can use the replaced-method element to replace an existing method implementation with another, for a deployed bean. Consider the following class, which has a method called computeValue that we want to override:

  • Java

  • Kotlin

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 接口的类提供新方法定义,如下面的示例所示:

A class that implements the org.springframework.beans.factory.support.MethodReplacer interface provides the new method definition, as the following example shows:

  • Java

  • Kotlin

/**
 * 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 定义类似于以下示例:

The bean definition to deploy the original class and specify the method override would resemble the following example:

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

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

You can use one or more <arg-type/> elements within the <replaced-method/> element to indicate the method signature of the method being overridden. The signature for the arguments is necessary only if the method is overloaded and multiple variants exist within the class. For convenience, the type string for an argument may be a substring of the fully qualified type name. For example, the following all match java.lang.String:

java.lang.String
String
Str

由于参数数量通常足以区分每种可能的选择,因此此快捷方式可以节省很多键入,因为它允许您仅键入与参数类型匹配的最短字符串。

Because the number of arguments is often enough to distinguish between each possible choice, this shortcut can save a lot of typing, by letting you type only the shortest string that matches an argument type.