使用 @Configuration 注解

@Configuration 是一个类级注解,指示对象是 bean 定义的来源。 @Configuration 类通过 @Bean 注解的方法声明 bean。 对 @Configuration 类上的 @Bean 方法的调用也可用于定义 bean 之间的依赖关系。 有关一般介绍,请参阅 基本概念:@Bean@Configuration

注入 Bean 之间的依赖关系

当 bean 彼此依赖时,表达这种依赖关系就像让一个 bean 方法调用另一个 bean 方法一样简单,如下例所示:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public BeanOne beanOne() {
    return new BeanOne(beanTwo());
  }

  @Bean
  public BeanTwo beanTwo() {
    return new BeanTwo();
  }
}

在前面的示例中,beanOne 通过构造函数注入接收对 beanTwo 的引用。

这种声明 bean 间依赖关系的方法仅当 @Bean 方法在 @Configuration 类中声明时才有效。 您不能使用普通的 @Component 类声明 bean 间依赖关系。

查找方法注入

如前所述,查找方法注入 是一项高级功能,您应很少使用。 在单例作用域 bean 依赖于原型作用域 bean 的情况下,它非常有用。 为此类型的配置使用 Java 提供了一种实现此模式的自然手段。 以下示例显示了如何使用查找方法注入:

  • Java

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

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

通过使用 Java 配置,您可以创建 CommandManager 的子类,其中抽象 createCommand() 方法被覆盖,以便它查找新的(原型)命令对象。 以下示例显示了如何执行此操作:

  • Java

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
  AsyncCommand command = new AsyncCommand();
  // 根据需要在此处注入依赖项
  return command;
}

@Bean
public CommandManager commandManager() {
  // 返回 CommandManager 的新匿名实现,其中 createCommand()
  // 被覆盖以返回新的原型 Command 对象
  return new CommandManager() {
    protected Command createCommand() {
      return asyncCommand();
    }
  };

}

关于基于 Java 的配置如何在内部工作的更多信息

考虑以下示例,该示例显示了被调用两次的 @Bean 注解方法:

  • Java

@Configuration
public class AppConfig {

  @Bean
  public ClientService clientService1() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
  }

  @Bean
  public ClientService clientService2() {
    ClientServiceImpl clientService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientService;
  }

  @Bean
  public ClientDao clientDao() {
    return new ClientDaoImpl();
  }
}

clientDao()clientService1() 中被调用了一次,在 clientService2() 中被调用了一次。 由于此方法创建一个 ClientDaoImpl 的新实例并返回它,因此您通常期望有两个实例(每个服务一个)。 这绝对是有问题的:在 Infra 中,实例化的 bean 默认具有 singleton 作用域。 这就是魔术的用武之地:所有 @Configuration 类在启动时都使用 CGLIB 进行子类化。 在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存的(作用域)bean。

根据 bean 的作用域,行为可能会有所不同。我们这里讨论的是单例。

无需将 CGLIB 添加到您的类路径中,因为 CGLIB 类已重新打包在 infra.cglib 包下,并直接包含在 infra-core JAR 中。

由于 CGLIB 在启动时动态添加功能,因此存在一些限制。 特别是,配置类不能是 final 的。 但是,配置类上允许任何构造函数,包括使用 @Autowired 或单个非默认构造函数声明进行默认注入。

如果您希望避免任何 CGLIB 强加的限制,请考虑在非 @Configuration 类上声明您的 @Bean 方法 (例如,在普通的 @Component 类上),或者用 @Configuration(proxyBeanMethods = false) 注解您的配置类。 这样,@Bean 方法之间的交叉方法调用就不会被拦截,因此您必须完全依赖构造函数或方法级别的依赖注入。