有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用应用程序属性动态创建bean

在具有spring-boot-starter-actuator依赖项的默认SpringBoot应用程序中,调用/actuator/health(使用show-details: true)返回以下信息

{
  "status": "UP",
  "components": { ... },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 2000396742656,
        "free": 581706977280,
        "threshold": 10485760,
        "exists": true
      }
    },
    "ping": { "status": "UP" }
  }
}

但是,我的机器上有两个驱动器,因此我想查看这两个驱动器的磁盘空间详细信息。因此,我添加了一个配置文件:

@Configuration
public class DiskSpaceConfig {
  @Bean
  public AbstractHealthIndicator driveC() {
    return new DiskSpaceHealthIndicator(new File("C:/"), DataSize.ofMegabytes(100));
  }

  @Bean
  public AbstractHealthIndicator driveD() {
    return new DiskSpaceHealthIndicator(new File("D:/"), DataSize.ofMegabytes(100));
  }
}

现在,我的健康端点返回以下数据

{
  "status": "UP",
  "components": { ... },
    "diskSpace": { "status": "UP", "details": { ... } },
    "driveC": { "status": "UP", "details": { ... } },
    "driveD": { "status": "UP", "details": { ... } },
    "ping": { "status": "UP" }
  }
}

(稍后我将考虑如何抑制默认的“diskSpace”条目)

但是,由于我可能希望在不同的机器上运行我的应用程序,我希望将磁盘驱动器配置为在我的application.yml文件中动态检查

media:
  health:
    drives:
      - name: driveC
        path: "C:/"
      - name: driveD
        path: "D:/"

还加

@Slf4j
@Component
@ConfigurationProperties(prefix = "media.health")
@Data
public class MediaHealthConfigLoader {
  private List<DriveConfig> drives;

  @PostConstruct
  public void postProcessBeanFactory() {
    log.info("Found {} configured drives", drives.size());
  }
}

@Data
public class DriveConfig {
  private String name;
  private String path;
}

@Configuration
@EnableConfigurationProperties
public class DiskSpaceConfig {
}

配置在启动期间加载并记录到控制台

然而,我的问题是,如何动态创建这些bean并将它们添加到bean上下文中?我想我需要去豆工厂。我尝试了以下方法:

@Slf4j
@Configuration
@EnableConfigurationProperties
@RequiredArgsConstructor
public class DiskSpaceConfig {
  private final MediaHealthConfigLoader mediaHealthConfigLoader;
  private final BeanFactory beanFactory;

  @PostConstruct
  public void createDiskSpaceHealthIndicators() {
    ArrayList<AbstractHealthIndicator> beans = new ArrayList<>();
    if (mediaHealthConfigLoader.getDrives() != null) {
      log.info("Creating {} dynamic DiskSpaceHealthIndicators", mediaHealthConfigLoader.getDrives().size());
      mediaHealthConfigLoader.getDrives().forEach(drive -> {
        DiskSpaceHealthIndicator indicator = new DiskSpaceHealthIndicator(new File(drive.getPath()), DataSize.ofMegabytes(100));
        log.info("Created indicator with name={} for path={}: bean={}", drive.getName(), drive.getPath(), indicator);
        
        // -> I would like to do something like: beanFactory.addBean(drive.getName(), indicator)
      });
    }
  }
}

但是,我的beanFactory只有getter,因此无法添加新创建的bean

我还尝试使用BeanFactoryPostProcessor钩住bean创建,但是,在执行回调时,配置属性(或加载它们的类)似乎还没有准备好

@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicHealthIndicatorFactory implements BeanFactoryPostProcessor {
  private final MediaHealthConfigLoader mediaHealthConfigLoader;

  @Override
  public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // mediaHealthConfigLoader is null here :-(

    mediaHealthConfigLoader.getDrives().forEach(drive -> {
        DiskSpaceHealthIndicator newBean = new DiskSpaceHealthIndicator(new File(drive.getPath()), DataSize.ofMegabytes(100));
        beanFactory.initializeBean(newBean, drive.getName());
        beanFactory.registerSingleton(drive.getName(), newBean);
    });
  }
}

或者

@Slf4j
@Component
@ConfigurationProperties(prefix = "media.health") // commenting out the same annotation on MediaHealthConfigLoader
public class DynamicHealthIndicatorFactory implements BeanFactoryPostProcessor {
  private List<DriveConfig> drives;

  @Override
  public void postProcessBeanFactory(final ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    // drives is null here :-(
    drives.forEach(drive -> {
        DiskSpaceHealthIndicator newBean = new DiskSpaceHealthIndicator(new File(drive.getPath()), DataSize.ofMegabytes(100));
        beanFactory.initializeBean(newBean, drive.getName());
        beanFactory.registerSingleton(drive.getName(), newBean);
    });
  }
}

但是,这两种方法都会导致NullPointerException,因为在生命周期的这一点上,属性和自动连线bean都没有准备好

我肯定我不是第一个尝试这样做的人,但似乎我没有找到正确的搜索词来找到解决方案


共 (1) 个答案

  1. # 1 楼答案

    这应该管用 参考:gist DemoApplication

    @Configuration
    @EnableConfigurationProperties
    @AllArgsConstructor
    class DiskSpaceConfig {
        private final MediaHealthConfigLoader mediaHealthConfigLoader;
        private final ConfigurableListableBeanFactory beanFactory;
    
        @PostConstruct
        public void registerBeans(){
            mediaHealthConfigLoader.getDrives().forEach(drive -> {
                DiskSpaceHealthIndicator newBean = new DiskSpaceHealthIndicator(new File(drive.getPath()), DataSize.ofMegabytes(100));
                beanFactory.initializeBean(newBean, drive.getName());
                beanFactory.registerSingleton(drive.getName(), newBean);
            });
        }
    }
    

    如果需要,可以禁用默认的磁盘空间

    management:
      health:
        diskspace:
          enabled: false