# 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
```

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.shiker.tech/spring-he-xin-gong-neng/1.ioc-rong-qi-he-bean-jian-jie/1.4.-yi-lai-xiang/1.4.6.-fang-fa-zhu-ru.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
