javaspring-bootspring-boot-actuatordynamic-properties

Multiple instances of a bean of one class filled with values from application.properties


I intend to write some HealtCheckContributors for a Spring Boot application using spring-boot-actuator. Hence, I implemented two of them. they are intended for checking the health of different apps, of course, but have a nearly identical structure, except the configuration properties, ...

SonarQube complains about that and I wonder if it is possible to have a single health check class but instantiated as many times as defined in application.properties. An example:

application.properties:

# actuator
app1.management.baseUrl=http://localhost:10000
app1.management.name=app1HealthCheckContributor
app2.management.basUrl=http://localhost:10001
app2.management.name=app2HealthCheckContributor

HealthCheckContributor for app1:

@Slf4j
@Component("xxx")
public class App1HealthCheckContributor extends AbstractHealthIndicator {

    private final App1Properties app1Properties;

    public App1HealthCheckContributor(final App1Properties app1Properties) {
        this.app1Properties = app1Properties;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) {...}
}

...and this code for each HealthCheckContributor only distinct in its appXProperties.

Isn't it possible to have some kind of base class like:

@Slf4j
@Component()
public class MyHealthCheckContributor extends AbstractHealthIndicator {

    private final MyProperties myProperties;

    public MyHealthCheckContributor(final MyProperties myProperties) {
        this.myProperties = myProperties;
    }

    @Override
    protected void doHealthCheck(Health.Builder builder) {...}
}

and let Spring Boot take care of instantiating two HealthCheckContributors (in our case App1HealthCheckContributor and App2HealthCheckContributor)? This would eliminate code duplication.

An example of the properties class file:

@Slf4j
@Data
@ConfigurationProperties(prefix = "app1.management")
public class App1Properties {
    private String baseUrl;
    private String ...;
}

How can I achieve this and how must an application.properties file looks like to achieve what I intend to do?

The final question: How to test multiple instance creation of a bean of one class filled with values from application.properties?


Solution

  • Assuming the code in doHealthCheck is exactly the same for all apps to be checked you could do the following.

    You would start by creating a single health check class:

    @Slf4j
    public class AppHealthCheckContributor extends AbstractHealthIndicator {
    
        private final AppProperties appProperties;
    
        public App1HealthCheckContributor(final AppProperties appProperties) {
            this.appProperties = appProperties;
        }
    
        @Override
        protected void doHealthCheck(Health.Builder builder) {...}
    }
    

    And the properties model as follows:

    @Slf4j
    @Data
    public class AppProperties {
        private String baseUrl;
        private String name;
    }
    

    This means that the configuration would be something like the following (in application.yml):

    health-check:
      apps:
        - baseUrl: http://localhost:10000
          name: app1
        - baseUrl: http://localhost:10001
          name: app2
    

    Finally, you would need to create a bean for each app and register them in the application context:

    @Slf4j
    @Data
    @Configuration
    @ConfigurationProperties(prefix = "health-check")
    public class AllAppPropertiesConfiguration {
        private List<AppProperties> apps;
    
        @Autowired
        private GenericApplicationContext applicationContext;
    
        @PostConstruct
        fun init() {
            for (AppProperties app : apps) {
                applicationContext.registerBean(app.getName(), AppHealthCheckContributor.class, app);
            }
        }
    }