-
Notifications
You must be signed in to change notification settings - Fork 41.1k
MeterRegistryPostProcessor dependency on ApplicationContext causes early context initialization #40525
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Unfortunately, the stack trace isn't useful as, while it tells us that a series of beans is being created, it doesn't tell us what they are. You can figure that out using the debugger by walking back up the stack and looking at the value of the |
beans are:
where actorSystemHolder is (most of the code is Scala codebase):
DatabaseInfo is just a bean inside SqlConfiguration:
where it extracts database information via connection.getMetadata:
DatabaseProxyFeature and MetricsTrackerFactory are making it a bit complicated but I can remove them. In that case list of beans is shorter:
|
Thanks for the details thus far. What's the bean that's being retrieved right at the bottom of the stack?
|
Hmmm I noticed that the bean itself is configured via xml config:
not sure if that makes difference |
It being defined in XML may make a difference. It may mean that the bean factory can't tell statically what type the
The XML snippet also shows that I'm afraid that we're going to need the complete picture here – and ideally a sample that reproduces the problem – to make any further progress. Injecting the application context into the constructor should not be a problem as there's special handling for that and it isn't treated like a normal bean. We can't change that without a complete understanding of the problem as it may not be the optimal solution and it may have unwanted side-effects that we couldn't justify without a full understanding. |
TBH I suspect it has something to do with XML vs Annotation config. In the meantime, i have traced this to the applicationContext passed via org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration#meterRegistryPostProcessor Maybe it would be fine to make this postprocessor ApplicationContextAware and invoke hasNoCompositeMeterRegistryBeans once postProcessAfterInitialization is invoked? i.e. class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializingSingleton, ApplicationContextAware {
private final ObjectProvider<MetricsProperties> properties;
private final ObjectProvider<MeterRegistryCustomizer<?>> customizers;
private final ObjectProvider<MeterFilter> filters;
private final ObjectProvider<MeterBinder> binders;
private volatile boolean deferBinding = true;
private final Set<MeterRegistry> deferredBindings = new LinkedHashSet<>();
private ApplicationContext applicationContext;
private boolean hasNoCompositeMeterRegistryBeans(ApplicationContext applicationContext) {
return applicationContext.getBeanNamesForType(CompositeMeterRegistry.class, false, false).length == 0;
}
MeterRegistryPostProcessor(ObjectProvider<MetricsProperties> properties,
ObjectProvider<MeterRegistryCustomizer<?>> customizers, ObjectProvider<MeterFilter> filters,
ObjectProvider<MeterBinder> binders) {
this.properties = properties;
this.customizers = customizers;
this.filters = filters;
this.binders = binders;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof MeterRegistry meterRegistry) {
postProcessMeterRegistry(meterRegistry);
}
return bean;
}
@Override
public void afterSingletonsInstantiated() {
synchronized (this.deferredBindings) {
this.deferBinding = false;
this.deferredBindings.forEach(this::applyBinders);
}
}
private void postProcessMeterRegistry(MeterRegistry meterRegistry) {
// Customizers must be applied before binders, as they may add custom tags or
// alter timer or summary configuration.
applyCustomizers(meterRegistry);
applyFilters(meterRegistry);
addToGlobalRegistryIfNecessary(meterRegistry);
if (isBindable(meterRegistry)) {
applyBinders(meterRegistry);
}
}
@SuppressWarnings("unchecked")
private void applyCustomizers(MeterRegistry meterRegistry) {
List<MeterRegistryCustomizer<?>> customizers = this.customizers.orderedStream().toList();
LambdaSafe.callbacks(MeterRegistryCustomizer.class, customizers, meterRegistry)
.withLogger(MeterRegistryPostProcessor.class)
.invoke((customizer) -> customizer.customize(meterRegistry));
}
private void applyFilters(MeterRegistry meterRegistry) {
if (meterRegistry instanceof AutoConfiguredCompositeMeterRegistry) {
return;
}
this.filters.orderedStream().forEach(meterRegistry.config()::meterFilter);
}
private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) {
if (this.properties.getObject().isUseGlobalRegistry() && !isGlobalRegistry(meterRegistry)) {
Metrics.addRegistry(meterRegistry);
}
}
private boolean isGlobalRegistry(MeterRegistry meterRegistry) {
return meterRegistry == Metrics.globalRegistry;
}
private boolean isBindable(MeterRegistry meterRegistry) {
return hasNoCompositeMeterRegistryBeans(applicationContext) || isCompositeMeterRegistry(meterRegistry);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private boolean isCompositeMeterRegistry(MeterRegistry meterRegistry) {
return meterRegistry instanceof CompositeMeterRegistry;
}
void applyBinders(MeterRegistry meterRegistry) {
if (this.deferBinding) {
synchronized (this.deferredBindings) {
if (this.deferBinding) {
this.deferredBindings.add(meterRegistry);
return;
}
}
}
this.binders.orderedStream().forEach((binder) -> binder.bindTo(meterRegistry));
}
} I tried this and it does not cause the checker to fail and I do not see any problems. Is there a chance to accept this change or at least somehow make processor instantiation "tweakable" (maybe check presence of the postprocessor with same name)? |
I tried to address this at the end of my previous comment:
I am still of the opinion that we should not change this without a full understanding of the problem. How the
You could perhaps use a bean factory post process to replace the definition of the |
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed. |
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue. |
Just to leave a pointer to someone with a similar problem: MeterRegistryPostProcessor caused early initialization of custom AuthenticationProvider (which had custom services injected via constructor - I changed it so that only ApplicationContext was injected and later lazily loaded those dependencies) and custom UserDetailsService that tried to resolve beans from application context (it really was just a composite pattern so I switched to I still do not fully understand why Spring had to fully resolve custom AuthenticationProvider (for custom UserDetailsService it was obvious), but I placed a breakpoint at org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency and followed stacktrace all the way up to the 1st bean that came from application itself. I had to repeat this process for 2 beans only. |
With recent spring-boot-actuator-autoconfigure-3.2.4.jar BeanPostProcessorChecker produces warnings when we apply MetricsAutoConfiguration:
The stacktrace of the 1st time it ends up in org.springframework.context.support.PostProcessorRegistrationDelegate.BeanPostProcessorChecker#postProcessAfterInitialization is:
I can see this is caused by meterRegistryPostProcessor trying to resolve application context:
I changed MetricsAutoConfiguration and MeterRegistryPostProcessor so that MeterRegistryPostProcessor implements ApplicationContextAware and does not need ApplicationContext as 1st constructor parameter - in that case I was able to avoid early application context initialization.
I cannot give a simple example because this happens in an old legacy app and I was unable to reproduce it outside of it.
If there is way to trace cause of this early initialization please let me know.
The text was updated successfully, but these errors were encountered: