简体   繁体   English

根据属性动态注册Spring个Bean

[英]Dynamically register Spring Beans based on properties

I'm using the latest Spring Boot version and trying to dynamically create n number of beans based upon what is defined in the application.yaml file.我正在使用最新的 Spring 引导版本并尝试根据application.yaml文件中定义的内容动态创建 n 个 bean。 I would then like to inject these beans into other classes based upon the bean name.然后我想根据 bean 名称将这些 bean 注入其他类。

The code below is a much simplified example of what I am trying to achieve.下面的代码是我想要实现的一个非常简化的例子。 The auto configuration would normally be part of a spring boot starter library so the number of beans needed to be registered is unknown.自动配置通常是 spring 引导启动程序库的一部分,因此需要注册的 bean 数量是未知的。

@Slf4j
@Value
public class BeanClass {

    private final String name;

    public void logName() {
        log.info("Name: {}", name);
    }

}
@Component
@RequiredArgsConstructor
public class ServiceClass {

    private final BeanClass fooBean;
    private final BeanClass barBean;

    public void log() {
        fooBean.logName();
        barBean.logName();
    }

}
@Value
@ConfigurationProperties
public class BeanProperties {

    private final List<String> beans;

}
@Configuration
public class AutoConfiguration {

    // Obviously not correct
    @Bean
    public List<BeanClass> beans(final BeanProperties beanProperties) {
        return beanProperties.getBeans().stream()
                .map(BeanClass::new)
                .collect(Collectors.toList());
    }

}
@EnableConfigurationProperties(BeanProperties.class)
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        final ServiceClass service = context.getBean(ServiceClass.class);
        service.log();
    }

}
beansToMake:
  - fooBean
  - barBean

I've tried multiple suggestions on google but nothing works and seems outdated.我在谷歌上尝试了多种建议,但没有任何效果,而且似乎已经过时了。 I'm hoping a new feature of Spring makes this straight forward.我希望 Spring 的一项新功能可以直接解决这个问题。

Here is a description of what you want (in a slightly simplified version):这是您想要的描述(稍微简化的版本):

https://www.baeldung.com/spring-same-class-multiple-beans https://www.baeldung.com/spring-same-class-multiple-beans

You need to register you own Implementation of BeanFactoryPostProcessor adding the functionality you need.您需要注册自己的BeanFactoryPostProcessor实现,添加您需要的功能。

@Configuration
public class MyAppConfig {
    @Bean
    public CustomBeanFactoryPostProcessor beanFactoryPostProcessor() {
        return new CustomBeanFactoryPostProcessor();
    }
}

Using your own implementation you will be able to register the beans manually using ConfigurableListableBeanFactory like this:使用您自己的实现,您将能够像这样使用ConfigurableListableBeanFactory手动注册 bean:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    ....
    beanFactory.registerSingleton(name, bean);
    ....

}

After all you need to create a generic factory, which will be used by the processor to create the beans.毕竟您需要创建一个通用工厂,处理器将使用它来创建 bean。 See the referenced baeldung article for some more examples.有关更多示例,请参阅参考的baeldung 文章

ImportBeanDefinitionRegistrar seems to be what you actually need: ImportBeanDefinitionRegistrar似乎是您真正需要的:

public class DynamicBeanRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

    @Setter
    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        BeanProperties properties = beanFactory.getBean(BeanProperties.class);

        for (String beanName : properties.getBeans()) {
            if (registry.containsBeanDefinition(beanName)) {
                continue;
            }

            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                    .genericBeanDefinition(BeanClass.class,
                            () -> new BeanClass(beanName))
                    .getBeanDefinition();
            registry.registerBeanDefinition(beanName, beanDefinition);
        }
    }


    @Configuration
    @EnableConfigurationProperties(BeanProperties.class)
    static class BeanPropertiesConfiguration {
        // spring parses nested classes first
    }

}
@EnableConfigurationProperties(BeanProperties.class)
// !!!
@Import(DynamicBeanRegistrar.class)
// !!!
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        final ServiceClass service = context.getBean(ServiceClass.class);
        service.log();
    }

}

You can implement BeanDefinitionRegistryPostProcessor interface to register BeanClass beans' definitions as follows:您可以实现BeanDefinitionRegistryPostProcessor接口来注册BeanClass bean 的定义,如下所示:

public class DynamicBeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor {

  public static final String PROPERTIES_PREFIX = "beans";
  private final List<String> beanNames;

  public DynamicBeanDefinitionRegistrar(Environment environment) {
    beanNames =
        Binder.get(environment)
            .bind(PROPERTIES_PREFIX, Bindable.listOf(String.class))
            .orElseThrow(IllegalStateException::new);
  }

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
      throws BeansException {
    beanNames.forEach(
        beanName -> {
          GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
          beanDefinition.setBeanClass(BeanClass.class);
          beanDefinition.setInstanceSupplier(() -> new BeanClass(beanName));
          registry.registerBeanDefinition(beanName, beanDefinition);
        });
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
      throws BeansException {}
}

Since properties are needed before beans are instantiated, to register BeanClass beans' definitions, @ConfigurationProperties are unsuitable for this case.由于在实例化 bean 之前需要属性来注册BeanClass bean 的定义, @ConfigurationProperties不适合这种情况。 Instead, Binder API is used to bind them programmatically.相反, Binder API用于以编程方式绑定它们。

Because BeanDefinitionPostProcessor objects must be instantiated very early in the container lifecycle, @Bean methods should be marked as static in @Configuration classes, according to Spring documentation .由于BeanDefinitionPostProcessor对象必须在容器生命周期的早期实例化,根据@Bean 文档,@Bean 方法应在@Configuration类中标记为 static。

@Configuration
public class DynamicBeanDefinitionRegistrarConfiguration {
  @Bean
  public static DynamicBeanDefinitionRegistrar beanDefinitionRegistrar(Environment environment) {
    return new DynamicBeanDefinitionRegistrar(environment);
  }
}

As result, all beans you define in application.yml , are registered as BeanClass beans:结果,您在application.yml中定义的所有 beans 都注册为BeanClass beans:

beans: 
    - fooBean
    - barBean

For reference: Create N number of beans with BeanDefinitionRegistryPostProcessor , Spring Boot Dynamic Bean Creation From Properties File供参考: 使用 BeanDefinitionRegistryPostProcessor 创建 N 个 beanSpring 从属性文件引导动态 Bean 创建

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM