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.
|
在上一段代码片段中的 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 的另一种方法是 Another way of accessing differently scoped target beans is an 您还可能发现 You may also find the |
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.