Reputation: 815
In my default SpringBoot application that has the spring-boot-starter-actuator
dependency, a call to /actuator/health
(with show-details: true
) returns the following information
{
"status": "UP",
"components": { ... },
"diskSpace": {
"status": "UP",
"details": {
"total": 2000396742656,
"free": 581706977280,
"threshold": 10485760,
"exists": true
}
},
"ping": { "status": "UP" }
}
}
However, I have two drives on my machine, so I would like to see the disk space details for both of them. So I added a configuration file:
@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));
}
}
Now, my health endpoint returns the following data
{
"status": "UP",
"components": { ... },
"diskSpace": { "status": "UP", "details": { ... } },
"driveC": { "status": "UP", "details": { ... } },
"driveD": { "status": "UP", "details": { ... } },
"ping": { "status": "UP" }
}
}
(I'll think about how to suppress the default "diskSpace" entry later)
However, as I might want to run my application on different machines, I would like to configure the disk drives to check dynamically in my application.yml
file.
media:
health:
drives:
- name: driveC
path: "C:/"
- name: driveD
path: "D:/"
Also adding
@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 {
}
The configuration is loaded during startup and logged to the console.
However, my question is, how do I dynamically create those beans and add them to the bean context? I assume I would need access to the BeanFactory. I've tried the following:
@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)
});
}
}
}
However, my beanFactory
only has getters, so there is no way to add my newly created beans.
I've also tried to hook into bean creation using a BeanFactoryPostProcessor
, however, it seems that the configuration properties (or the class loading them) are not ready when that callback is executed.
@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);
});
}
}
alternatively
@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);
});
}
}
However, both of these approaches cause NullPointerExceptions because neithe the properties nor the autowired bean are ready at this point of the lifecycle.
I'm sure I'm not the first person trying to do something like this, but it seems I fail to find the correct search terms to find a solution.
Upvotes: 2
Views: 2408
Reputation: 6936
This should work ref: 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);
});
}
}
You can disable the default diskspace if you want
management:
health:
diskspace:
enabled: false
Upvotes: 2