Skip to content
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

missing reflection data for CGLIB$SET_THREAD_CALLBACKS in configuration properties class #32645

Closed
cmdjulian opened this issue Apr 14, 2024 · 6 comments
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue theme: aot An issue related to Ahead-of-time processing

Comments

@cmdjulian
Copy link

cmdjulian commented Apr 14, 2024

I included a third party spring boot starter. That starter includes a configuration properties class. This class is a kotlin data class and employs Hibernate validator for some validation logic.

@Validated
@ConfigurationProperties(prefix = "mqtt")
data class MqttProperties(
    @get:NotEmpty
    val host: String = "",
    ...
)

Not quite sure why that is though, but this class gets stubbed on runtime by a Spring cglib proxy. I suspect it's due to the validation logic. When now building a native image, this cglib proxy seems to not get register with native image. The following error is generated when trying to run the native executable:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'de.smartsquare.starter.mqtt.MqttSubscriberCollector': Instantiation of supplied bean failed
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1223) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[demo:6.1.5]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:277) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:805) ~[demo:6.1.5]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[demo:6.1.5]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[demo:3.2.4]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[demo:3.2.4]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[demo:3.2.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[demo:3.2.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[demo:3.2.4]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[demo:3.2.4]
	at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:13) ~[demo:na]
	at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) ~[na:na]
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class de.smartsquare.starter.mqtt.MqttProperties: Common causes of this problem include using a final class or a non-visible class
	at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:227) ~[demo:6.1.5]
	at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:160) ~[demo:6.1.5]
	at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[na:na]
	at org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.buildLazyResolutionProxy(ContextAnnotationAutowireCandidateResolver.java:136) ~[na:na]
	at org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.buildLazyResolutionProxy(ContextAnnotationAutowireCandidateResolver.java:84) ~[na:na]
	at org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.getLazyResolutionProxyIfNecessary(ContextAnnotationAutowireCandidateResolver.java:54) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1347) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904) ~[na:na]
	at org.springframework.beans.factory.support.RegisteredBean.resolveAutowiredArgument(RegisteredBean.java:229) ~[demo:6.1.5]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:341) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveArguments(BeanInstanceSupplier.java:264) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:204) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1217) ~[demo:6.1.5]
	... 18 common frames omitted
Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.NoSuchMethodException-->de.smartsquare.starter.mqtt.MqttProperties$$SpringCGLIB$$0.CGLIB$SET_THREAD_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;)
	at org.springframework.cglib.proxy.Enhancer$EnhancerFactoryData.<init>(Enhancer.java:501) ~[na:na]
	at org.springframework.cglib.proxy.Enhancer.wrapCachedClass(Enhancer.java:801) ~[na:na]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:108) ~[na:na]
	at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52) ~[na:na]
	at [email protected]/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[demo:na]
	at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:57) ~[na:na]
	at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[na:na]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:130) ~[na:na]
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:317) ~[demo:6.1.5]
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:562) ~[na:na]
	at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:407) ~[na:na]
	at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:62) ~[na:na]
	at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:218) ~[demo:6.1.5]
	... 31 common frames omitted
Caused by: java.lang.NoSuchMethodException: de.smartsquare.starter.mqtt.MqttProperties$$SpringCGLIB$$0.CGLIB$SET_THREAD_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;)
	at [email protected]/java.lang.Class.checkMethod(DynamicHub.java:1075) ~[demo:na]
	at [email protected]/java.lang.Class.getDeclaredMethod(DynamicHub.java:1165) ~[demo:na]
	at org.springframework.cglib.proxy.Enhancer.getCallbacksSetter(Enhancer.java:901) ~[na:na]
	at org.springframework.cglib.proxy.Enhancer$EnhancerFactoryData.<init>(Enhancer.java:490) ~[na:na]
	... 43 common frames omitted

When duplicating the configuration properties class in the main source set of the application the error does not happen and the native image generated works out of the box without any additional configuration. I also tried adding reflect config via , which did not solve the @RegisterReflectionForBinding(MqttProperties::class) problem though.

When adding the following reflect-config.json the error is gone:

[
  {
    "name": "de.smartsquare.starter.mqtt.MqttProperties$$SpringCGLIB$$0",
    "fields": [
      {
        "name": "CGLIB$CALLBACK_FILTER"
      },
      {
        "name": "CGLIB$FACTORY_DATA"
      }
    ],
    "methods": [
      {
        "name": "CGLIB$SET_THREAD_CALLBACKS",
        "parameterTypes": [
          "org.springframework.cglib.proxy.Callback[]"
        ]
      }
    ]
  }
]

I feel like that I as a developer should not be responsible for generating implementation detail code like reflection hints for cglib reflections. I think the code should work without any modifications out of the box.

I prepared a demo application which show cases the problem when generating it with ./gradlew nativeCompile and then running the native binary.

Is there something I'm missing here? Whats the suggested approach for a library owner which wants to support native-image for a custom spring starter?

Unsure, but maybe related to #29873?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 14, 2024
@cmdjulian cmdjulian changed the title missing reflection data for cglib from external configuration properties class missing reflection data for CGLIB$SET_THREAD_CALLBACKS in configuration properties class Apr 14, 2024
@wilkinsona
Copy link
Member

Thanks for the report.

There's quite a lot going on in the demo application. There are many classes that I don't think are related to the problem. Kotlin is also involved which makes things quite a bit more complicated, particularly when using GraalVM. Can you please reduce the classes involved to the bare minimum required to reproduce the problem and, unless the problem only occurs with Kotlin, please write those classes in Java.

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Apr 15, 2024
@cmdjulian
Copy link
Author

I minified the example in the repository. Please check again. I now identified the main problem. It is not related to kotlin. The main problem seems to be the @Lazy in the BeanPostProcessor. Without that @Lazy the tests run successfully.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Apr 15, 2024
@cmdjulian
Copy link
Author

cmdjulian commented Apr 15, 2024

I also tried refactoring my code to use ObjectProvider<MqttProperties> via class MqttSubscriberCollector(private val config: ObjectProvider<MqttProperties>) : BeanPostProcessor but this seems to not work here, as the following error is reported: Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'mqtt-de.smartsquare.starter.mqtt.MqttProperties': Requested bean is currently in creation: Is there an unresolvable circular reference?

Any other way I could ditch the @Lazy as a workaround?

@wilkinsona
Copy link
Member

Thanks very much for minimising the sample.

Using ObjectProvider<MqttProperties> seems to work for me (with your Java-based sample) so I'm not sure what's happening for you when you try it using Kotlin.

The use of @Lazy causes MqttProperties to be proxied. Ideally, Spring Framework's AOT support would generate the necessary reflection metadata for this automatically but that isn't happening. I also tried using a @Bean method with a @Lazy parameter:

@Bean
static MqttSubscriberCollector mqttSubscriverCollectior(@Lazy MqttProperties properties) {
    return new MqttSubscriberCollector(properties);
}

My goal was to make the use of @Lazy more obvious such that it would be automatically detected by Framework but it was not successful.

We'll transfer this to the Framework team so that they can investigate.

@bclozel bclozel transferred this issue from spring-projects/spring-boot Apr 16, 2024
@jhoeller jhoeller added in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing labels Apr 16, 2024
@cmdjulian
Copy link
Author

Yeah I got it working with ObjectProvider meanwhile. Was just a stupid mistake to eagerly call the getObject() method inside the bean post processors constructor. You were right, that seems to workaround this. Thanks for investigating 😃

@snicoll
Copy link
Member

snicoll commented Apr 18, 2024

Thanks for the report, I believe this is a duplicate of #30985 and we'll validate this sample when we get to it.

@snicoll snicoll closed this as not planned Won't fix, can't repro, duplicate, stale Apr 18, 2024
@snicoll snicoll added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Apr 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue theme: aot An issue related to Ahead-of-time processing
Projects
None yet
Development

No branches or pull requests

5 participants