方法注入

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

一种解决方案是放弃一些控制反转。 您可以通过实现 ApplicationContextAware 接口 让 bean A 知道容器, 并通过 对容器进行 getBean("B") 调用 在每次 bean A 需要时请求(通常是新的)bean B 实例。 以下示例显示了这种方法:

  • Java

package fiona.apple;

// Infra-API imports
import infra.beans.BeansException;
import infra.context.ApplicationContext;
import infra.context.ApplicationContextAware;

/**
 * 一个使用有状态 Command 风格类来执行某些处理的类。
 */
public class CommandManager implements ApplicationContextAware {

  private ApplicationContext applicationContext;

  public Object process(Map commandState) {
    // 获取适当 Command 的新实例
    Command command = createCommand();
    // 设置(希望是全新的)Command 实例的状态
    command.setState(commandState);
    return command.execute();
  }

  protected Command createCommand() {
    // 注意 Infra API 依赖!
    return this.applicationContext.getBean("command", Command.class);
  }

  public void setApplicationContext(
      ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}

前面的做法是不可取的,因为业务代码知道并耦合到 TODAY Framework。 方法注入是 Infra IoC 容器的一个有点高级的功能,它可以让您干净地处理这个用例。

您可以在 这篇博文 中阅读更多关于方法注入动机的内容。

查找方法注入

查找方法注入是容器覆盖容器管理 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。 查找通常涉及原型 bean,如 上一节 中描述的场景。 TODAY Framework 通过使用 CGLIB 库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。

  • 为了使这种动态子类化起作用,Infra bean 容器子类化的类不能是 final,要覆盖的方法也不能是 final

  • 对具有 abstract 方法的类进行单元测试需要您自己子类化该类并提供 abstract 方法的存根实现。

  • 具体方法对于组件扫描也是必要的,这需要具体类来拾取。

  • 另一个关键限制是查找方法不适用于工厂方法,特别是配置类中的 @Bean 方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类。

在前面代码片段中的 CommandManager 类的情况下,Infra 容器动态覆盖 createCommand() 方法的实现。 CommandManager 类没有任何 Infra 依赖项,如重写的示例所示:

  • Java

package fiona.apple;

// 不再有 Infra 导入!

public abstract class CommandManager {

  public Object process(Object commandState) {
    // 获取适当 Command 接口的新实例
    Command command = createCommand();
    // 设置(希望是全新的)Command 实例的状态
    command.setState(commandState);
    return command.execute();
  }

  // 好的... 但是这个方法的实现哪里去了?
  protected abstract Command createCommand();
}

在包含要注入的方法的客户端类中(本例中为 CommandManager),要注入的方法需要以下形式的签名:

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

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

<!-- 部署为原型(非单例)的有状态 bean -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
  <!-- 根据需要在此处注入依赖项 -->
</bean>

<!-- commandProcessor 使用 statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
  <lookup-method name="createCommand" bean="myCommand"/>
</bean>

标识为 commandManager 的 bean 在每次需要 myCommand 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();
}

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

访问不同作用域目标 bean 的另一种方法是 ObjectFactory/Provider 注入点。 请参阅 作用域 Bean 作为依赖项

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

任意方法替换

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

对于基于 XML 的配置元数据,您可以使用 replaced-method 元素将已部署 bean 的现有方法实现替换为另一种方法实现。 考虑以下类,它有一个名为 computeValue 的方法,我们想要覆盖它:

  • Java

public class MyValueCalculator {

  public String computeValue(String input) {
    // 一些真实代码...
  }

  // 一些其他方法...
}

实现 infra.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如下例所示:

  • Java

/**
 * 旨在用于覆盖 MyValueCalculator 中现有的 computeValue(String) 实现
 */
public class ReplacementComputeValue implements MethodReplacer {

  public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
    // 获取输入值,处理它,并返回计算结果
    String input = (String) args[0];
    ...
    return ...;
  }
}

部署原始类并指定方法覆盖的 bean 定义类似于以下示例:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
  <!-- 任意方法替换 -->
  <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

java.lang.String
String
Str

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