With recent spring-boot-actuator-autoconfigure-3.2.4.jar BeanPostProcessorChecker produces warnings when we apply MetricsAutoConfiguration:
2024-04-25 17:59:57.980 [main] {} WARN org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'distributedFeaturesConfiguration' of type [com.xebialabs.xlrelease.features.distributed.DistributedFeaturesConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected into a currently created BeanPostProcessor [meterRegistryPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies.
The stacktrace of the 1st time it ends up in org.springframework.context.support.PostProcessorRegistrationDelegate.BeanPostProcessorChecker#postProcessAfterInitialization is:
Breakpoint reached
at org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization(PostProcessorRegistrationDelegate.java:437)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:438)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1789)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$766/0x00000008015fe6f0.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:409)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$766/0x00000008015fe6f0.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:782)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:237)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1355)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1192)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$766/0x00000008015fe6f0.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:409)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$766/0x00000008015fe6f0.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:696)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:203)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1355)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1192)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getSingletonFactoryBeanForTypeCheck(AbstractAutowireCapableBeanFactory.java:996)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:891)
at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:621)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:575)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:534)
at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:260)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1637)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1397)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:782)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:542)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$766/0x00000008015fe6f0.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:277)
at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:805)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:334)
at com.xebialabs.xlrelease.XLReleaseBootstrapper$.main(XLReleaseBootstrapper.scala:182)
at com.xebialabs.xlrelease.XLReleaseBootstrapper.main(XLReleaseBootstrapper.scala:-1)
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.
Comment From: wilkinsona
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 beanName parameter for each occurrence of ….createBean(AbstractAutowireCapableBeanFactory.java:522). If you can share with us those bean names plus details of how each is defined, we may be able to figure out the cause.
Comment From: ilx
beans are:
- meterRegistryPostProcessor
- actorSystemHolder
- xlrDatabaseInformation
- sqlConfiguration
- databaseProxyFeature
- distributedFeaturesConfiguration
where actorSystemHolder is (most of the code is Scala codebase):
class ActorSystemHolder(xlrConfig: XlrConfig, @Qualifier("xlrDatabaseInformation") dbInfo: DatabaseInfo)
extends Logging with ScalaSpringAwareBean with FactoryBean[ActorSystem] {
private var running: Boolean = false
lazy val actorSystem: ActorSystem = {
info("Starting up actor system.")
if (xlrConfig.isActiveOnStartup) {
NodeState.setActive(true)
}
val pekkoConfig = getSlickConfig.withFallback(xlrConfig.pekko.config)
val system = ActorSystem(xlrConfig.pekko.systemName, pekkoConfig)
system.registerExtension(SpringExtension)
running = true
system
}
def shutdown(): Unit = {
if (running) {
info("Initiating actor system shutdown.")
if (!AutoDowning.isDowning) {
// Actor system should be terminated by Coordinated Shutdown Pekko extension
val result = CoordinatedShutdown.get(actorSystem).run(GracefulShutdownReason)
info("Waiting for actor system shutdown (indefinitely).")
Await.result(result, atMost = Duration.Inf)
}
running = false
info("Actor system shutdown finished.")
}
}
override def getObject: ActorSystem = actorSystem
override def getObjectType: Class[_] = classOf[ActorSystem]
DatabaseInfo is just a bean inside SqlConfiguration:
@Bean
def xlrDatabaseInformation: DatabaseInfo = {
DatabaseInfo(xlrRepositoryDataSourceProxy())
}
where it extracts database information via connection.getMetadata:
object DatabaseInfo {
def apply(ds: DataSource): DatabaseInfo = {
map(ds.getConnection) { connection =>
val metadata = connection.getMetaData
DatabaseInfo(metadata)
}
}
class SqlConfiguration(xlrConfig: XlrConfig,
databaseProxyFeature: DatabaseProxyFeature,
metricsTrackerFactory: MetricsTrackerFactory
)
DatabaseProxyFeature and MetricsTrackerFactory are making it a bit complicated but I can remove them. In that case list of beans is shorter: - meterRegistryPostProcessor - actorSystemHolder - xlrDatabaseInformation - sqlConfiguration
Comment From: wilkinsona
Thanks for the details thus far. What's the bean that's being retrieved right at the bottom of the stack?
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:277)
Comment From: ilx
Bean at the bottom of the stack is meterRegistryPostProcessor
Comment From: ilx
Hmmm I noticed that the bean itself is configured via xml config:
<bean id="actorSystemHolder" class="com.xebialabs.xlrelease.actors.ActorSystemHolder" destroy-method="shutdown"
depends-on="beforeLiquibaseUpgrade">
<constructor-arg name="xlrConfig" ref="xlrConfig"/>
<constructor-arg name="dbInfo" ref="xlrDatabaseInformation" />
</bean>
not sure if that makes difference
Comment From: wilkinsona
It being defined in XML may make a difference. It may mean that the bean factory can't tell statically what type the com.xebialabs.xlrelease.actors.ActorSystemHolder factory bean will produce so it's being created really early to figure that out. Defining it in an @Configuration class may help with that as may using ObjectProvider to injects its arguments as injection into a FactoryBean is a known cause of problems:
- https://github.com/spring-projects/spring-framework/issues/13893
- https://github.com/spring-projects/spring-framework/issues/24553
The XML snippet also shows that beforeLiquibaseUpgrade is involved here too. That will affect things.
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.
Comment From: ilx
TBH I suspect it has something to do with XML vs Annotation config. It would take me some time to create reproducible sample (as this just works elsewhere).
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. I'm not sure if there is another post processor that uses the same pattern...
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)?
Comment From: wilkinsona
I tried this and it does not cause the checker to fail and I do not see any problems. I'm not sure if there is another post processor that uses the same pattern...
Is there a chance to accept this change
I tried to address this at the end of my previous comment:
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.
I am still of the opinion that we should not change this without a full understanding of the problem. How the ApplicationContext is injected should not make any difference. Apparently it is making a difference so there's something unknown here that we don't understand. Until we do, it would be hard to justify making a change.
somehow make processor instantiation "tweakable" (maybe check presence of the postprocessor with same name)
You could perhaps use a bean factory post process to replace the definition of the MeterRegistryPostProcessor. In your situation, I would invest time in creating a reproducer rather than resorting to such a hack.
Comment From: spring-projects-issues
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.
Comment From: spring-projects-issues
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.