根据属性动态注册Spring Bean

soo*_*892 7 java spring spring-boot

我正在使用最新的 Spring Boot 版本,并尝试根据文件中定义的内容动态创建 n 个 bean application.yaml。然后我想根据 bean 名称将这些 bean 注入到其他类中。

下面的代码是我想要实现的目标的一个非常简化的示例。自动配置通常是 spring boot starter 库的一部分,因此需要注册的 bean 数量未知。

@Slf4j
@Value
public class BeanClass {

    private final String name;

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

}
Run Code Online (Sandbox Code Playgroud)
@Component
@RequiredArgsConstructor
public class ServiceClass {

    private final BeanClass fooBean;
    private final BeanClass barBean;

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

}
Run Code Online (Sandbox Code Playgroud)
@Value
@ConfigurationProperties
public class BeanProperties {

    private final List<String> beans;

}
Run Code Online (Sandbox Code Playgroud)
@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());
    }

}
Run Code Online (Sandbox Code Playgroud)
@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();
    }

}
Run Code Online (Sandbox Code Playgroud)
beansToMake:
  - fooBean
  - barBean
Run Code Online (Sandbox Code Playgroud)

我在谷歌上尝试了多种建议,但没有任何效果,而且似乎已经过时了。我希望 Spring 的一个新功能能让这一切变得简单。

Ton*_*oni 11

您可以实现BeanDefinitionRegistryPostProcessor接口来注册BeanClassbean 的定义,如下所示:

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 {}
}
Run Code Online (Sandbox Code Playgroud)

由于在实例化 bean 之前需要属性,因此注册BeanClassbean 的定义@ConfigurationProperties不适合这种情况。相反,Binder API用于以编程方式绑定它们。

根据Spring 文档,由于BeanFactoryPostProcessor对象通常必须在容器生命周期的早期实例化,因此@Bean应在类中将方法标记为静态@Configuration以避免生命周期问题。

@Configuration
public class DynamicBeanDefinitionRegistrarConfiguration {
  @Bean
  public static DynamicBeanDefinitionRegistrar beanDefinitionRegistrar(Environment environment) {
    return new DynamicBeanDefinitionRegistrar(environment);
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,您在 中定义的所有 beanapplication.yml都会注册为BeanClassbeans:

beans: 
    - fooBean
    - barBean
Run Code Online (Sandbox Code Playgroud)

供参考:使用 BeanDefinitionRegistryPostProcessor 创建 N 个 beanSpring Boot Dynamic Bean Creation From Properties File