使用 BeanDefinitionRegistryPostProcessor 的 Spring 动态 Bean

问题描述 投票:0回答:1

我最近了解到通过从配置属性中获取必要的数据来动态创建bean的可能性,这可以通过实现接口BeanDefinitionRegistryPostProcessor来实现。

我只找到了使用

Bindable.listOf
的教程,比如 spring 配置属性绑定 提供的教程,而我需要使用
Bindable.mapOf
,因为我的配置属性是:

config:
  project:
    my-config-map:
      x:
        name: X-a
        age: 1
      y:
        name: Y-a
        age: 2
      z:
        name: Z-a
        age: 3

其中 my-config-map 是 String 作为键、MyConfig 作为值的映射

@Data
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MyConfig {

    private String name;

    private Integer age;

}

我创建了以下 BeanDefinitionRegistrar 类:

public class MyBeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor {

    public MyBeanDefinitionRegistrar(Environment environment, String property) {
        configurations = Binder.get(environment).bind(property, Bindable.mapOf(String.class, MyConfig.class))
                .orElseThrow(IllegalStateException::new);
    }

    private final Map<String, MyConfig> configurations;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        configurations.entrySet().forEach(entry -> registerBeanDefinition(registry, entry));
    }

    private void registerBeanDefinition(BeanDefinitionRegistry registry, Entry<String, MyConfig> entry) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(MyBean.class);
        beanDefinition.setInstanceSupplier(getBeanInstanceSupplier(entry));
        registry.registerBeanDefinition(getBeanName(entry), beanDefinition);
    }

    private Supplier<?> getBeanInstanceSupplier(Entry<String, MyConfig> entry) {
        MyConfig myConfig = entry.getValue();
        return () -> MyBean.builder().name(myConfig.getName()).age(myConfig.getAge()).build();
    }

    private String getBeanName(Entry<String, MyConfig> entry) {
        return entry.getKey();
    }

}

并按以下方式使用:

@Configuration
public class MyConfiguration {

    @Bean
    MyBeanDefinitionRegistrar myBeanDefinitionRegistrar(Environment environment) {
        return new MyBeanDefinitionRegistrar(environment, "config.project.my-config-map");
    }

}

但是一旦我启动 Spring Boot 应用程序,它就会在 MyBeanDefinitionRegistrar 的构造函数上出现错误,它无法创建

堆栈跟踪:

2024-05-06 15:35:46.047 | main | ERROR| org.springframework.boot.SpringApplication |  | Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myBeanDefinitionRegistrar' defined in class path resource [io/github/paulmarcelinbejan/coandaairlines/reservationsystem/configuration/MyConfiguration.class]: Failed to instantiate [io.github.paulmarcelinbejan.coandaairlines.reservationsystem.configuration.MyBeanDefinitionRegistrar]: Factory method 'myBeanDefinitionRegistrar' threw exception with message: null
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:639) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:141) ~[spring-context-6.1.4.jar:6.1.4]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:788) ~[spring-context-6.1.4.jar:6.1.4]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:606) ~[spring-context-6.1.4.jar:6.1.4]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) [spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) [spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) [spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) [spring-boot-3.2.3.jar:3.2.3]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) [spring-boot-3.2.3.jar:3.2.3]
    at io.github.paulmarcelinbejan.coandaairlines.reservationsystem.application.CoandaAirlinesReservationSystemApplication.main(CoandaAirlinesReservationSystemApplication.java:20) [classes/:?]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.github.paulmarcelinbejan.coandaairlines.reservationsystem.configuration.MyBeanDefinitionRegistrar]: Factory method 'myBeanDefinitionRegistrar' threw exception with message: null
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:177) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:647) ~[spring-beans-6.1.4.jar:6.1.4]
    ... 19 more
Caused by: java.lang.IllegalStateException
    at org.springframework.boot.context.properties.bind.BindResult.orElseThrow(BindResult.java:125) ~[spring-boot-3.2.3.jar:3.2.3]
    at io.github.paulmarcelinbejan.coandaairlines.reservationsystem.configuration.MyBeanDefinitionRegistrar.<init>(MyBeanDefinitionRegistrar.java:19) ~[classes/:?]
    at io.github.paulmarcelinbejan.coandaairlines.reservationsystem.configuration.MyConfiguration.myBeanDefinitionRegistrar(MyConfiguration.java:12) ~[classes/:?]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140) ~[spring-beans-6.1.4.jar:6.1.4]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:647) ~[spring-beans-6.1.4.jar:6.1.4]
    ... 19 more
java spring spring-boot
1个回答
0
投票

我怀疑您的属性未正确加载到环境中,因为在删除属性之前我无法重现它。例如,您可以通过临时将以下行添加到您的

@Bean
方法来确认这一点:

System.out.println(environment.getProperty("config.project.my-config-map.x.name"));

此外,返回

@Bean
类型的
BeanFactoryPostProcessor
方法应在
@Configuration
类中标记为静态,以避免生命周期问题,因为根据 Spring 文档,它们必须在容器生命周期的早期实例化。

© www.soinside.com 2019 - 2024. All rights reserved.