方法注入

在大多数应用程序场景中,容器中的大多数 bean 都是单例的。当单例 bean 需要 与另一个单例 bean 协作或非单例 bean 需要协作 对于另一个非单例 bean,通常通过定义一个 bean 作为另一个的属性。当 bean 生命周期为 不同。假设单例 bean A 需要使用非单例(原型)bean B, 可能在 A 上的每个方法调用时。容器仅创建单例 bean A 一次,因此只有一次设置属性的机会。容器不能 每次需要 bean B 时,都会向 bean A 提供一个新的 bean B 实例。spring-doc.cadn.net.cn

一个解决方案是放弃一些倒置的控制。您可以通过实现ApplicationContextAware接口 以及制作一个getBean("B")对容器的调用ask for (一个 通常是 new) 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 框架。方法注入,Spring IoC 的一个有点高级的功能 容器,让您能够干净利落地处理此用例。spring-doc.cadn.net.cn

您可以在此博客文章中阅读有关 Method Injection 动机的更多信息。spring-doc.cadn.net.cn

查找方法注入

Lookup 方法注入是容器覆盖 container-managed bean 中,并返回 容器。查找通常涉及原型 Bean,如所描述的场景所示 在上一节中。Spring 框架 通过使用 CGLIB 库中的字节码生成来实现此方法注入 动态生成覆盖该方法的 subclass。spring-doc.cadn.net.cn

  • 要使这个动态子类化工作,Spring bean 容器 子类不能为final,并且要覆盖的方法不能为final也。spring-doc.cadn.net.cn

  • 对具有abstractmethod 要求您将类子类化 自己,并提供abstract方法。spring-doc.cadn.net.cn

  • 对于组件扫描来说,具体方法也是必要的,这需要具体的 课程。spring-doc.cadn.net.cn

  • 另一个关键限制是 lookup 方法不能与工厂方法一起使用,并且 特别是 Not with@Bean方法,因为在这种情况下, 容器不负责创建实例,因此无法创建 运行时生成的动态子类。spring-doc.cadn.net.cn

CommandManager类中, Spring 容器动态覆盖createCommand()方法。这CommandManagerclass 没有任何 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在这个 case),则要注入的方法需要以下形式的签名:spring-doc.cadn.net.cn

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

如果 method 为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>

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

标识为commandManager调用自己的createCommand()方法 每当它需要myCommand豆。您必须小心部署 这myCommandbean 作为原型,如果这确实是需要的。如果是 一个单例,则myCommandbean 的 bean 中。spring-doc.cadn.net.cn

或者,在基于注释的组件模型中,您可以声明一个查找 方法通过@Lookupannotation 中,如下例所示: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 与 Lookup 方法的 declared return 类型: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注射点。请参见将范围限定的 Bean 作为依赖项spring-doc.cadn.net.cn

您还可以找到ServiceLocatorFactoryBean(在org.springframework.beans.factory.configpackage) 才能有用。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.MethodReplacerinterface 提供新的方法定义,如下例所示: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/>元素来指示被覆盖的方法的方法签名。签名 仅当方法重载且有多个变体时,才需要参数 存在于类中。为方便起见,参数的类型字符串可以是 完全限定类型名称的 substring 的 substring 中。例如,以下 all matchjava.lang.String:spring-doc.cadn.net.cn

java.lang.String
String
Str

因为参数的数量通常足以区分每种可能的 选项,此快捷方式可以节省大量键入,因为允许您只键入 与参数类型匹配的最短字符串。spring-doc.cadn.net.cn