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,@EnableAutoConfigurationand@Importthat 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
DefaultHealthContributorRegistrygets instanciated byHealthEndpointConfigurationimported byHealthEndpointAutoConfiguration - on the application that does not get its health indicator exposed:
DefaultHealthContributorRegistrygets instanciated byHealthEndpointConfigurationimported byHealthEndpointAutoConfigurationand application bean health indicator is instanciated after and is never registered inDefaultHealthContributorRegistry
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
MeterRegistryPostProcessorthat took the followingMeterRegistryCustomizeras 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
prometheusHealthCheckbean triggered the creation ifDefaultHealthContributorRegistryas it requiredHealthEndpoint....
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.