@Configuration 是一个类级注解,表示一个对象是 Bean 定义的来源。@Configuration 类通过 @Bean 注解的方法声明 bean。对 @Configuration 类上的 @Bean 方法的调用也可以用来定义 Bean 的依赖关系。参见基本概念 @Bean 和 @Configuration 的介绍

注入 bean 之间的依赖

当 Bean 相互之间有依赖关系时,表达这种依赖关系就像让一个 Bean 方法调用另一个一样简单,正如下面的例子所示:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public BeanOne beanOne() {
  5. return new BeanOne(beanTwo());
  6. }
  7. @Bean
  8. public BeanTwo beanTwo() {
  9. return new BeanTwo();
  10. }
  11. }

在前面的例子中,BeanOne 通过构造函数注入收到了对 BeanTwo 的引用。

:::info 这种声明 bean 之间依赖关系的方法只有在 @Configuration 类中声明了 @Bean 方法时才有效。你不能通过使用普通的 @Component 类来声明 bean 间的依赖关系。 :::

查找方法注入

如前所述,查找方法注入 是一个高级功能,你应该很少使用。在单例作用域的 Bean 对多例作用域的 Bean 有依赖性的情况下,它很有用。为这种类型的配置使用 Java 提供了实现这种模式的自然手段。下面的例子展示了如何使用查找方法注入:

  1. public abstract class CommandManager {
  2. public Object process(Object commandState) {
  3. // 创建一个 command 接口的新的实例
  4. Command command = createCommand();
  5. // 希望在全新的实例对象上设置状态
  6. command.setState(commandState);
  7. return command.execute();
  8. }
  9. // 那么这个抽象方法的实现在哪里呢?
  10. protected abstract Command createCommand();
  11. }

通过使用 Java 配置,你可以创建一个 CommandManager 的子类,其中抽象的 createCommand() 方法被重载,这样它就可以查找到一个新的(多例)命令对象。下面的例子显示了如何做到这一点:

一个抽象类

  1. package cn.mrcode.study.springdocsread.web;
  2. /**
  3. * @author mrcode
  4. */
  5. public abstract class CommandManager {
  6. public Command process(Object commandState) {
  7. // 创建 Command 的实例
  8. Command command = createCommand();
  9. return command;
  10. }
  11. // 这个方法的实现在哪里呢?
  12. protected abstract Command createCommand();
  13. }
  1. public class Command {
  2. }

使用配置方式实例化,提供方法注入的效果

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.context.annotation.Scope;
  5. /**
  6. * @author mrcode
  7. */
  8. @Configuration
  9. public class AppConfig {
  10. @Bean
  11. @Scope("prototype")
  12. public AsyncCommand asyncCommand() {
  13. AsyncCommand command = new AsyncCommand();
  14. // 根据需要在这里注入依赖性
  15. return command;
  16. }
  17. @Bean
  18. public CommandManager commandManager() {
  19. // 用 createCommand() 返回 CommandManager 的新匿名实现。
  20. // 重载以返回一个新的 多例的 Command 对象
  21. return new CommandManager() {
  22. protected Command createCommand() {
  23. return asyncCommand();
  24. }
  25. };
  26. }
  27. }
  1. package cn.mrcode.study.springdocsread.web;
  2. /**
  3. * @author mrcode
  4. */
  5. public class AsyncCommand extends Command{
  6. }

:::tips 不使用这个 AsyncCommand 直接创建 Command 也是一样的;
它这种方式使用匿名 内部类的方式共享了 bean 方法的代理效果。其实 CommandManager 的 createCommand() 方法被我们自己实现了,不过是在 @Configuration 类中调用的 @Bean 方法,参数的多例 :::

有关基于 Java 的配置如何在内部工作的进一步信息

考虑下面的例子,它显示了一个 @Bean 注解的 clientDao 方法被调用了两次:

  1. @Configuration
  2. public class AppConfig {
  3. @Bean
  4. public ClientService clientService1() {
  5. ClientServiceImpl clientService = new ClientServiceImpl();
  6. clientService.setClientDao(clientDao());
  7. return clientService;
  8. }
  9. @Bean
  10. public ClientService clientService2() {
  11. ClientServiceImpl clientService = new ClientServiceImpl();
  12. clientService.setClientDao(clientDao());
  13. return clientService;
  14. }
  15. @Bean
  16. public ClientDao clientDao() {
  17. return new ClientDaoImpl();
  18. }
  19. }

clientDao()clientService1()clientService2()中被调用了一次。由于该方法创建了一个新的 ClientDaoImpl 实例并将其返回,你通常会期望有两个实例(每个服务都有一个)。这肯定是有问题的:在 Spring 中,实例化的 Bean 默认有一个单例作用域。这就是神奇之处。所有的@Configuration类都是在启动时用 CGLIB 进行子类化的。在子类中,子方法首先检查容器中是否有任何缓存(作用域)的 Bean,然后再调用父方法并创建一个新实例。

:::info 根据你的 Bean 的范围,其行为可能是不同的。我们在这里讨论的是单例。 ::: :::info 从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的 classpath 中,因为 CGLIB 类已经被重新打包到org.springframework.cglib 下,并直接包含在 spring-core JAR 中。 ::: :::tips 由于 CGLIB 在启动时动态地添加功能,所以有一些限制。特别是,配置类不能是 final 的。然而,从 4.3 开始,配置类允许任何构造函数,包括使用 @Autowired 或单一的非默认构造函数声明进行默认注入。

如果你想避免任何 CGLIB 施加的限制,可以考虑在非 @Configuration 类中声明你的 @Bean 方法(例如,在普通的 @Component 类中)。这样,@Bean 方法之间的跨方法调用就不会被拦截,所以你必须完全依赖构造函数或方法层面的依赖注入。 :::