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 楼答案
这应该管用 参考:gist DemoApplication
如果需要,可以禁用默认的磁盘空间