spring - ApplicationContext registerBean 自动装配失败但 getBean 在 Spring 5 中工作

spring - ApplicationContext registerBean autowiring fails but getBean works in Spring 5

我正在使用使用动态 bean 注册的配置 class:

@Configuration
public class ConfigClass {

    @Autowired
    private GenericApplicationContext applicationContext;

    @PostConstruct
    private void init() {
        System.out.println("init");
        applicationContext.registerBean("exService", ExecutorService.class, () -> Executors.newFixedThreadPool(10), bd -> bd.setAutowireCandidate(true));
        System.out.println("init done");
    }
}

如果我尝试自动装配 bean,应用程序启动失败并出现错误 Field exService in com.example.DemoApplication required a bean of type 'java.util.concurrent.ExecutorService' that could not be found.

从日志中我可以看到在错误之前没有调用 config class 上的 init 方法,因为没有打印出两个系统输出语句。

但是,当我使用 applicationContext.getBean(ExecutorService.class) 时,它确实可以正常工作。

我能把 bean 拿到 Autowire 吗?

我故意不使用 @Bean 注解,因为我需要根据特定条件动态注册 bean。

这可能是因为您在上下文初始化阶段的中间注册了您的 bean。如果您的目标 bean 在调用 ConfigClass @PostConstruct 之前初始化并且 auto-wires ExecutorService 则根本没有可用的 bean。

您可以尝试强制初始化顺序:

@Component
@DependsOn("configClass")
public class MyComponent

  @Autowired
  private ExecutorService executorService;

然而,使用 BeanFactoryPostProcessorBeanDefinitionBuilder:

注册一个 bean 定义会更清晰
@Component
public class MyBeanRegistration implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
    BeanDefinitionRegistry reg = (BeanDefinitionRegistry) bf;
    reg.registerBeanDefinition("exService",
      BeanDefinitionBuilder
        .rootBeanDefinition(ExecutorService.class)
        .setFactoryMethod("newWorkStealingPool")
        .getBeanDefinition());
  }

}

实际上,我编写了处理此类问题的小型基础架构。这是如何做到这一点的想法:

  • 创建一个 class(我们称之为 MyClass),将 ExecutorService.class 封装为 属性 并将其声明为 Bean (@Component)。甚至在此之前创建一个接口(让我们称之为 MyInterface),你的新 class 将实现
  • 使用方法 MyInterface getInstance(String) 创建一个工厂 class(我们称它为 MyFactory),返回一个接口实例。在该工厂中创建一个静态 属性 Map[String, MyInterface] 和 public 静态方法,允许您将接口的实例添加到此映射
  • 在 MyClass 中创建一个构造函数,该构造函数最后将使用您的 class 名称 ("MyClass")
  • 的键将新创建的自身实例放入工厂中的映射中

现在 诀窍是,当 Spring 启动并初始化时,它会创建所有的 bean。当您的 MyClass 将被创建时,其构造函数会将其实例放入工厂中。所以现在你可以在代码的任何地方调用:

MyInterface myInterface = MyFactory.getInstance("MyClass");

你得到了你的 bean,而不用担心实例化它。 Spring 已经为您完成了。巨大的额外好处是 non-intrucivness - 你不必明确地使用 Spring classes

你可以这样做:

@Resource
@Lazy
private ExecutorService executorService;

有效。