# 1.4.6. 方法注入

在大多数应用场景中，容器中的大多数 bean 都是 [单例](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-singleton)的。当一个单例 bean 需要与另一个单例 bean 协作或非单例 bean 需要与另一个非单例 bean 协作时，您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。当 bean 生命周期不同时，就会出现问题。假设单例 bean A 需要使用非单例（原型）bean B，可能在 A 上的每个方法调用上。容器只创建一次单例 bean A，因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供一个新的 bean B 实例。

一个解决方案是放弃一些控制反转。您可以通过实现[`ApplicationContextAware`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-aware)接口将 bean A 织入到容器，并在每次bean A 需要时通过[`getBean("B")`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-client)方法调用容器来请求（通常是新的）bean B 实例。以下示例显示了这种方法：

```java
// 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 框架。方法注入是 Spring IoC 容器的一项高级功能，可让您干净地处理此用例。

[您可以在此博客条目](https://spring.io/blog/2004/08/06/method-injection/)中阅读有关方法注入动机的更多信息 。

**查找方法注入**

查找方法注入是容器覆盖容器管理的 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及一个原型 bean，如上[一节](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-method-injection)中描述的场景。Spring 框架通过使用 CGLIB 库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。

要使这种动态子类化工作，Spring bean 容器子类的类不能是`final`，要覆盖的方法也不能是`final` 。对具有`abstract`方法的类进行单元测试需要您自己子类化该类并提供该`abstract`方法的存根实现。组件扫描也需要具体的方法，这需要具体的类来拾取。另一个关键限制是查找方法不适用于工厂方法，特别是不适用于配置类中的`@Bean`方法，因为在这种情况下，容器不负责创建实例，因此无法创建运行时生成的子类苍蝇。

在前面代码片段中的`CommandManager`类的情况下，Spring 容器会动态覆盖`createCommand()` 方法的实现。类`CommandManager`没有任何 Spring 依赖项，如重新设计的示例所示：

```java
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` ），要注入的方法需要以下形式的签名：

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

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

```xml
<!-- 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>
```

每当需要 `myCommand bean` 的新实例时，标识为 `commandManager` 的 bean 都会调用自己的`createCommand()`方法。如果实际需要的话，您必须小心地将 `myCommand bean` 部署为原型。如果是单例，则每次都会返回 `myCommand bean` 的相同实例。

或者，在基于注解的组件模型中，您可以通过注解声明查找方法`@Lookup`，如以下示例所示：

```java
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：

```java
public abstract class CommandManager {

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

    @Lookup
    protected abstract Command createCommand();
}
```

请注意，您通常应该使用具体的存根实现声明此类带注解的查找方法，以使它们与默认情况下忽略抽象类的 Spring 组件扫描规则兼容。此限制不适用于显式注册或显式导入的 bean 类。

访问不同范围的目标 bean 的另一种方法是`ObjectFactory`/ `Provider`注入点。请参阅[Scoped Beans as Dependencies](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-other-injection)。您可能还会发现`ServiceLocatorFactoryBean`（在 `org.springframework.beans.factory.config`包中）很有用。

**任意方法替换**

与查找方法注入相比，一种不太有用的方法注入形式是能够用另一种方法实现替换托管 bean 中的任意方法。在您真正需要此功能之前，您可以放心地跳过本节的其余部分。

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

```java
public class MyValueCalculator {

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

    // some other methods...
}
```

实现`org.springframework.beans.factory.support.MethodReplacer` 接口的类提供了新的方法定义，如以下示例所示：

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

```xml
<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
java.lang.String
String
Str
```

因为参数的数量通常足以区分每个可能的选择，所以这个快捷方式可以节省大量的输入，让您只输入与参数类型匹配的最短字符串。
