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 bean class in native image with a Kotlin nested class #32472

Closed
juliuskrah opened this issue Mar 18, 2024 · 5 comments
Closed

Missing bean class in native image with a Kotlin nested class #32472

juliuskrah opened this issue Mar 18, 2024 · 5 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing theme: kotlin An issue related to Kotlin support type: bug A general bug
Milestone

Comments

@juliuskrah
Copy link

juliuskrah commented Mar 18, 2024

Affects: <6.1.2>
Reproducer: https://github.com/juliuskrah/graphql-demo/tree/spf-32472

Missing class in AOT processed Bean

Given the following bean definition:

class MongockBeanDefinitionRegistrar(
    private val environment: Environment
): ImportBeanDefinitionRegistrar {
// ...

    override fun registerBeanDefinitions(
        metadata: AnnotationMetadata,
        registry: BeanDefinitionRegistry,
        importBeanNameGenerator: BeanNameGenerator,
    ) {
      // ...
       val mongockSupportBeanDefinitionBuilder = BeanDefinitionBuilder
            .rootBeanDefinition(MongockRunnerSupport::class.java)
            .addPropertyValue("migrationClasses", changeUnitSets)
            .addPropertyReference("driver", "connectionDriver")
            .addPropertyReference("config", "mongock-io.mongock.runner.springboot.base.config.MongockSpringConfiguration")

        val mongockRunnerBeanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MongockRunner::class.java)
            .setFactoryMethodOnBean("create", "mongockRunnerSupport")
            .setInitMethodName("execute")

        registry.registerBeanDefinition(getName(MongockRunner::class.java), mongockRunnerBeanDefinitionBuilder.beanDefinition)
        registry.registerBeanDefinition("mongockRunnerSupport", mongockSupportBeanDefinitionBuilder.beanDefinition)
  }

  class MongockRunnerSupport: ApplicationContextAware, ApplicationEventPublisherAware {
        var driver: ConnectionDriver? = null
        var config: MongockConfiguration? = null
        var migrationClasses: List<Class<*>>? = emptyList()
        private lateinit var applicationContext: ApplicationContext
        private lateinit var eventPublisher: ApplicationEventPublisher

        fun create(): MongockRunner {
            val builder: RunnerSpringbootBuilder = MongockSpringboot.builder()
            if (this.driver != null) builder.setDriver(driver)
            if (this.config != null) builder.setConfig(config)
            builder.setSpringContext(applicationContext)
            builder.setEventPublisher(eventPublisher)
            migrationClasses?.forEach(builder::addMigrationClass)
            return builder.buildRunner()
        }

        override fun setApplicationContext(applicationContext: ApplicationContext) {
            this.applicationContext = applicationContext
        }

        override fun setApplicationEventPublisher(applicationEventPublisher: ApplicationEventPublisher) {
            this.eventPublisher = applicationEventPublisher
        }
    }
}

The aot process generates the following class:

@Generated
public class MongockBeanDefinitionRegistrar__BeanDefinitions {

  @Generated
  public static class MongockRunnerSupport {

    private static BeanInstanceSupplier<MongockRunner> getMongockRunnerInstanceSupplier() {
      return BeanInstanceSupplier.<MongockRunner>forFactoryMethod(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class, "create")
              .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class).create());
    }


    public static BeanDefinition getMongockRunnerBeanDefinition() {
      RootBeanDefinition beanDefinition = new RootBeanDefinition(MongockRunner.class);
      beanDefinition.setTargetType(MongockRunner.class);
      beanDefinition.setInitMethodNames("execute");
      beanDefinition.setInstanceSupplier(getMongockRunnerInstanceSupplier());
      return beanDefinition;
    }


    public static BeanDefinition getMongockRunnerSupportBeanDefinition() {
      RootBeanDefinition beanDefinition = new RootBeanDefinition(MongockBeanDefinitionRegistrar.MongockRunnerSupport.class);
      beanDefinition.getPropertyValues().addPropertyValue("migrationClasses", List.of(CreateProductCollectionChangeUnit202401151530.class));
      beanDefinition.getPropertyValues().addPropertyValue("driver", new RuntimeBeanReference("connectionDriver"));
      beanDefinition.getPropertyValues().addPropertyValue("config", new RuntimeBeanReference("mongock-io.mongock.runner.springboot.base.config.MongockSpringConfiguration"));
      beanDefinition.setInstanceSupplier(MongockBeanDefinitionRegistrar.MongockRunnerSupport::new);
      return beanDefinition;
    }
  }
}

I encounter the following exception when running the native executable:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongockRunnerSupport': Unresolved class: class com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport (kind = CLASS)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1323) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1284) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:486) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:341) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334) ~[graphql-demo:6.1.2]
	at com.example.graph.spring.MongockBeanDefinitionRegistrar__BeanDefinitions$MongockRunnerSupport.lambda$getRunnerSpringbootBuilderInstanceSupplier$0(MongockBeanDefinitionRegistrar__BeanDefinitions.java:28) ~[na:na]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[graphql-demo:6.1.2]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:171) ~[na:na]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[graphql-demo:6.1.2]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206) ~[na:na]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[graphql-demo:6.1.2]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:218) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206) ~[na:na]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1216) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[graphql-demo:6.1.2]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:960) ~[graphql-demo:6.1.2]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[graphql-demo:6.1.2]
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[graphql-demo:3.2.1]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) ~[graphql-demo:3.2.1]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:464) ~[graphql-demo:3.2.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[graphql-demo:3.2.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1358) ~[graphql-demo:3.2.1]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1347) ~[graphql-demo:3.2.1]
	at com.example.graph.ApplicationKt.main(Application.kt:16) ~[graphql-demo:na]
	at java.base@21/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) ~[na:na]
Caused by: kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Unresolved class: class com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport (kind = CLASS)
	at kotlin.reflect.jvm.internal.KClassImpl.createSyntheticClassOrFail(KClassImpl.kt:340) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl.access$createSyntheticClassOrFail(KClassImpl.kt:49) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:67) ~[na:na]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$descriptor$2.invoke(KClassImpl.kt:53) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data.getDescriptor(KClassImpl.kt:53) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl.getDescriptor(KClassImpl.kt:193) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl.getMemberScope$kotlin_reflection(KClassImpl.kt:202) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:173) ~[na:na]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:173) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(KClassImpl.kt:173) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:182) ~[na:na]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:182) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllNonStaticMembers(KClassImpl.kt:182) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$allMembers$2.invoke(KClassImpl.kt:188) ~[na:na]
	at kotlin.reflect.jvm.internal.KClassImpl$Data$allMembers$2.invoke(KClassImpl.kt:188) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:70) ~[na:na]
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllMembers(KClassImpl.kt:188) ~[graphql-demo:1.9.21-release-633]
	at kotlin.reflect.jvm.internal.KClassImpl.getMembers(KClassImpl.kt:206) ~[graphql-demo:1.9.21-release-633]
	at org.springframework.data.util.KotlinBeanInfoFactory.getBeanInfo(KotlinBeanInfoFactory.java:64) ~[graphql-demo:3.2.1]
	at org.springframework.beans.CachedIntrospectionResults.getBeanInfo(CachedIntrospectionResults.java:222) ~[na:na]
	at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:248) ~[na:na]
	at org.springframework.beans.CachedIntrospectionResults.forClass(CachedIntrospectionResults.java:157) ~[na:na]
	at org.springframework.beans.BeanWrapperImpl.getCachedIntrospectionResults(BeanWrapperImpl.java:162) ~[graphql-demo:6.1.2]
	at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:193) ~[graphql-demo:6.1.2]
	at org.springframework.beans.BeanWrapperImpl.getLocalPropertyHandler(BeanWrapperImpl.java:58) ~[graphql-demo:6.1.2]
	at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyHandler(AbstractNestablePropertyAccessor.java:739) ~[graphql-demo:6.1.2]
	at org.springframework.beans.AbstractNestablePropertyAccessor.isWritableProperty(AbstractNestablePropertyAccessor.java:565) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1686) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1433) ~[graphql-demo:6.1.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[graphql-demo:6.1.2]
	... 41 common frames omitted
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 18, 2024
@sdeleuze sdeleuze self-assigned this Mar 18, 2024
@sdeleuze sdeleuze added the theme: aot An issue related to Ahead-of-time processing label Mar 18, 2024
@sdeleuze
Copy link
Contributor

When running docker compose -f src/main/resources/compose.yml -f src/main/resources/compose.dev.yml up, I get a permissions on /docker-entrypoint-initdb.d/keyfile are too open error with Docker 24.0.5, could you please fix it to allow me to run the sample properly?

@sdeleuze sdeleuze added the status: waiting-for-feedback We need additional information before we can continue label Mar 18, 2024
@juliuskrah
Copy link
Author

When running docker compose -f src/main/resources/compose.yml -f src/main/resources/compose.dev.yml up, I get a permissions on /docker-entrypoint-initdb.d/keyfile are too open error with Docker 24.0.5, could you please fix it to allow me to run the sample properly?

Set the permissions on the file

chmod 400 src/main/resources/docker/keyfile

@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 Mar 18, 2024
@sdeleuze
Copy link
Contributor

sdeleuze commented Mar 18, 2024

I am able to reproduce, that said, I am not sure something has to be fixed in Spring Framework since org.springframework.data.util.KotlinBeanInfoFactory is triggering this error. But we can try to qualify more precisely this issue.

I am not sure yet why we see this error because com.example.graph.spring.MongockBeanDefinitionRegistrar$MongockRunnerSupport seems to have various metadata provided, and even if I add allDeclaredMethods: true and "allPublicMethods": true I still see the same error (see related kotlin-reflect source code).

@sdeleuze sdeleuze added the theme: kotlin An issue related to Kotlin support label Mar 18, 2024
@sdeleuze
Copy link
Contributor

After more tests, I found that the applications works as expected if you add a reflection hint for the outer class com.example.graph.spring.MongockBeanDefinitionRegistrar (currently missing).

@snicoll The code path involves Spring Data which invokes kotlinClass.getMembers() which requires the outer class hint, but maybe we could handle that defensively at Framework level. Any thoughts?

Unrelated, notice that an additional hint for io.mongock.runner.core.executor.MongockRunnerImpl is also needed to be able to detect the execute function and can't be, by design, guessed by the inference mechanism if I am not mistaken.

@sdeleuze sdeleuze changed the title Missing bean class in native image when inner class Missing bean class in native image with a Kotlin nested class Mar 18, 2024
@sdeleuze sdeleuze added this to the 6.1.6 milestone Mar 18, 2024
@sdeleuze sdeleuze added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Mar 18, 2024
@snicoll
Copy link
Member

snicoll commented Mar 19, 2024

After chatting with Sébastien, we think that we should detect this case whenever we infer a reflection hint on a type. We also wonder if the same issue would happen if a non-static inner class was exposed that way in Java.

@jhoeller jhoeller added the in: core Issues in core modules (aop, beans, core, context, expression) label Mar 27, 2024
@sdeleuze sdeleuze modified the milestones: 6.1.6, 6.1.x Apr 9, 2024
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Apr 10, 2024
This is needed by Kotlin reflection in order to be able to list
class members on native.

Closes spring-projectsgh-32472
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) theme: aot An issue related to Ahead-of-time processing theme: kotlin An issue related to Kotlin support type: bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants