Hi

I am upgading a mono repo multi app project on springboot 2.7 / spring cloud 2021.0.3.

In this repository I have multiple auto-configured libraries, used by some applications.

  • all libraries exposes bean instantiated by auto configuration
  • all application are annotated with @SpringBootConfiguration, @EnableAutoConfiguration and @Import that list the application configurations (simple configuration in the application module not listed in auto configuration)

I implemented this pattern so as not to have some component scanning.

Here is the issue:

In the auto configuration and in the application configuration there are some health indicators. Everything works fine but two applications that follows the same pattern. On these two applications, the health indicator declared in the application module (annotated Bean declared in application configuration that is not an auto configuration) are not exposed. Other applications following the same pattern gets all their health indicator exposed

I debug the starting of the applications

  • on an application that gets its health indicator exposed: health indicator is instanciated then DefaultHealthContributorRegistry gets instanciated by HealthEndpointConfiguration imported by HealthEndpointAutoConfiguration
  • on the application that does not get its health indicator exposed: DefaultHealthContributorRegistry gets instanciated by HealthEndpointConfiguration imported by HealthEndpointAutoConfiguration and application bean health indicator is instanciated after and is never registered in DefaultHealthContributorRegistry

From what I understood, bean in simple configuration were instanciated before auto configuration... Yet this is not the case so I must have missed something.

What is sure is something changed in the behaviour between spring 2.6 and 2.7.

I hope I was clear enough.

Thank you.

Comment From: wilkinsona

Generally speaking, things should happen in two separate phases. Firstly, beans are registered. Secondly, beans are created. In the situation where things aren't working as you'd expect, it sounds like something has triggered the creation of the DefaultHealthContributorRegistry before bean registration has completed. There's no one specific cause of this problem so, unfortunately, I can't offer and further guidance based on what you've described thus far. If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

Comment From: antechrestos

@wilkinsona thank you for the feedback. As a I understand there is no specific cause leading to a bean being created before every bean are registered? (I guess that registration phase is some kind of dependency tree building so as to order bean creation in a second phase)

Comment From: wilkinsona

As a I understand there is no specific cause leading to a bean being created before every bean are registered?

Correct. One possible cause is accidental creation of a bean in a bean factory post-processor but there are others. Your problem may also have a different cause entirely. I can't tell with the information provided thus far.

I guess that registration phase is some kind of dependency tree building so as to order bean creation in a second phase

Not exactly. Registration (largely) doesn't know anything about dependencies between beans. It's only once creation begins that the inter-dependencies really become apparent.

As I said above, please provide a minimal sample that reproduces the problem. This will allow us to fully understand it.

Comment From: antechrestos

@wilkinsona Turns out it was a messy issue on my side

  • my health indicator required a component
  • this late component required a feign client
  • this late one required some beans declared in WebMvcAutoConfiguration.EnableWebMvcConfiguration
  • it needed meter registry configurer
  • this one called the MeterRegistryPostProcessor that took the following MeterRegistryCustomizer as candidate
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> prometheusHealthCheck(HealthEndpoint healthEndpoint) {
        return registry -> registry.gauge("application_health_status", healthEndpoint,
                (HealthEndpoint endpoint) -> endpoint.health().getStatus().equals(Status.UP) ? 1.0 : 0.0);
    }

This late one was implemented due to an OPS request to display the health status as a single prometheus gauge

  • then this prometheusHealthCheck bean triggered the creation if DefaultHealthContributorRegistry as it required HealthEndpoint....

Turning the above bean into

@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, HealthEndpointAutoConfiguration.class })
public class ActuateAutoConfiguration {

    ActuateAutoConfiguration(MeterRegistry meterRegistry,
                             HealthEndpoint healthEndpoint){
        meterRegistry.gauge("application.health.status", healthEndpoint, (HealthEndpoint endpoint) -> {
            Status status = endpoint.health().getStatus();
            if (Status.UP.equals(status)) {
                return 1.0;
            } else if (Status.DOWN.equals(status) || Status.OUT_OF_SERVICE.equals(status)) {
                return 0.0;
            }
            return 0.5;
        } );
    }
}

cracked the problem. This late approach seem clearer as it does not expose any bean and just configure two different components once they are ready without creating any dependency.

Thank you for your help

Comment From: wilkinsona

I'm pleased to hear you got to the bottom of it. Thanks for letting us know.