【问题标题】:@Component beans override @Bean ones for collection autowiring@Component bean 覆盖 @Bean 用于集合自动装配
【发布时间】:2020-12-08 16:10:11
【问题描述】:

我有一个非常简单的 Spring Boot 测试应用程序。

它只有一个 Dog 类和 @SpringBootApplication 注解的类。 我创建了两个 Dog bean,一切都按预期运行。


public class Dog {
    
    public String name;
    
    public Dog() {
        this("noname");
    }

    public Dog(String name) {
        this.name = name;
    }

    public String toString() {
        return name;
    }
}

@SpringBootApplication
public class DemoApplication {

    @Autowired
    private List<Dog> dogs;
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    @Bean
    CommandLineRunner runner1() {
        return args -> {
            for (Dog d : dogs) {
                System.out.println(d);
            }
        };
    }

    
    @Bean
    Dog laika() {
        return new Dog("laika");
    }

    @Bean
    Dog lassie() {
        return new Dog("lassie");
    }
}

输出:

laika
lassie

但是现在我向 Dog 类添加了一个 @Component 注释,期望现在我将得到三个 Dog 类型的 bean,如果我用另一个像这样的 CommandLineRunner 打印所有 bean,这似乎会发生:

    @Bean
      public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {
          System.out.println("Let's inspect the beans provided by Spring Boot:");
          String[] beanNames = ctx.getBeanDefinitionNames();
          Arrays.sort(beanNames);
          for (String beanName : beanNames) {
            System.out.println(beanName);
          }
        };
    }

输出:

Let's inspect the beans provided by Spring Boot:
applicationAvailability
applicationTaskExecutor
commandLineRunner
demoApplication
dog
laika
lassie
lifecycleProcessor
...

然而,当我使用我的第一个 CommandLineRunner 打印出我的 Dog 列表的内容时,我得到的唯一输出是:

noname

似乎@Component bean 使 @Bean 声明的 bean 消失以进行集合注入。我观察到任何 bean 的相同行为,例如,如果我在独立的 @Component 类中声明更多 CommandLineRunners,每个人都会运行,但是当我在列表中 @Autowire 时,只有用 @Component 声明的那些被注入。

不过,我仍然可以使用其他 Dog bean。例如,如果我用 @Primary 注释 Laika bean,它将作为方法参数注入,但 @Autowire 的集合没有任何变化。

【问题讨论】:

  • 这是一个奇怪的边缘情况,IMO 不值得深入研究,但这说明了为什么在 @Configuration 类中注入 bean 列表可能是一个坏主意(这也负责他们的初始化)。相反,如果你真的想要 Dog bean 的列表,请在 runner1 @Bean 方法中指定 List&lt;Dog&gt; 参数,Spring 将负责将它们全部注入。
  • 如果您真的很感兴趣,请在here 中放置一个断点。这就是差异发生的地方。本质上,当dogs 被填充时,Spring 将只考虑在@Configuration 类(链接中的isSelfReference)之外声明的bean,但前提是有。如果没有,它也会使用@Bean 方法。

标签: java spring spring-boot autowired


【解决方案1】:

您的启动应用程序以一种有趣的方式, 但不管怎样,这段代码

    @Bean
CommandLineRunner runner1() {
    return args -> {
        for (Dog d : dogs) {
            System.out.println(d);
        }
    };
}

当它调用时,其他2只狗还没有初始化,所以在狗列表中你只有@component注释的一个,即“noname”

【讨论】:

  • 这很奇怪,因为如果我在同一个 CommandLineRunner 中将所有 bean 打印到 ApplicationContext 中,我可以看到其他 2 条狗已初始化。此外,为什么如果我在 Dog 类中注释掉 @Component 注释,它们会在那时被初始化?
  • 这并不奇怪,当你有了 ctx 时,一切都完成了,但是在 CommandLineRunner runner1() 里面就像我说的,那一刻,ctx 还没有准备好,它仍然在创建 rest bean,为了确认这一点,如果你在应用程序启动后调用方法 runner1(),你会看到 3 条狗,
  • 但是为什么他们的两个例子有区别呢?
  • 一切都与时间有关,当 Spring 应用程序准备好时,您将拥有所有 3 个 bean,当应用程序正在准备时,就像在方法 runner1() 中一样,您将只有 1 个 bean,而不是其他 2 个bean,因为它们还没有创建,当 runner1() 方法完成时,Spring 将继续使用 @Bean 注解的 laika() 和 lassie() 方法。
  • 在第二种情况下,方法 @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) 只会在 Spring 准备好后调用,因为您有参数 ApplicationContext ctx 作为输入,Spring 将等待直到它创建 ApplicationContext在调用此方法之前,但在上 runner1() 中,该方法在 SpringApplication 内部,将在 Spring 准备上下文期间调用,这就是您没有完成 bean 列表的原因
猜你喜欢
  • 2017-04-29
  • 2014-06-30
  • 2016-06-08
  • 2015-04-20
  • 1970-01-01
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多