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

Configuration class with Bean factory method on an interface generates wrong target with AOT #32609

Closed
MartinLei opened this issue Apr 9, 2024 · 6 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing type: bug A general bug
Milestone

Comments

@MartinLei
Copy link

Spring-Boot version 3.2.4

The processAot task does not generate the right BeanDefinitions files, if a configuration class uses composition of beans trough interfaces.

See showcase project

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 9, 2024
@bclozel bclozel transferred this issue from spring-projects/spring-boot Apr 9, 2024
@snicoll snicoll self-assigned this Apr 10, 2024
@snicoll
Copy link
Member

snicoll commented Apr 10, 2024

@MartinLei thanks for the sample but the README instructs to list a file without additional information. The app starts fine so I am not really sure what you mean.

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

Thank @snicoll for your prompt feedback.
Please excuse my very short README.md. I added more details how to reproduce the problem for spring native builds.

I hope this makes it more clear :)

@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 10, 2024
@snicoll
Copy link
Member

snicoll commented Apr 10, 2024

This is working fine with AOT on the JVM. It does fail on native with:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'createACar': Instantiation of supplied bean failed
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:344) ~[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]
	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:200) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[demo:6.1.5]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[demo:6.1.5]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[demo:6.1.5]
	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.DemoApplication.main(DemoApplication.java:14) ~[demo:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'createACar': 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:200) ~[demo:6.1.5]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[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) ~[na:na]
	at org.springframework.beans.factory.aot.BeanInstanceSupplier.resolveAutowiredArgument(BeanInstanceSupplier.java:341) ~[na:na]
	... 20 common frames omitted
Caused by: java.lang.UnsupportedOperationException: CGLIB runtime enhancement not supported on native image. Make sure to include a pre-generated class on the classpath instead: com.example.demo.config.BeanA$$SpringCGLIB$$FastClass$$0
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[demo:6.1.5]
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:107) ~[na:na]
	at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52) ~[na:na]
	at java.base@17.0.10/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[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.reflect.FastClass$Generator.create(FastClass.java:70) ~[na:na]
	at org.springframework.cglib.proxy.MethodProxy.helper(MethodProxy.java:148) ~[demo:6.1.5]
	at org.springframework.cglib.proxy.MethodProxy.init(MethodProxy.java:89) ~[demo:6.1.5]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:256) ~[demo:6.1.5]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[na:na]
	at com.example.demo.config.BeanConfig$$SpringCGLIB$$0.createACar(<generated>) ~[demo:na]
	at com.example.demo.config.BeanA__BeanDefinitions.lambda$getCreateACarInstanceSupplier$0(BeanA__BeanDefinitions.java:19) ~[na:na]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[demo:6.1.5]
	at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[demo:6.1.5]
	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) ~[demo:6.1.5]
	at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[demo:6.1.5]
	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) ~[demo:6.1.5]
	at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[demo:6.1.5]
	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) ~[demo:6.1.5]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1217) ~[demo:6.1.5]
	... 33 common frames omitted

@snicoll snicoll added type: bug A general bug theme: aot An issue related to Ahead-of-time processing 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 10, 2024
@snicoll snicoll added this to the 6.1.x milestone Apr 10, 2024
@snicoll snicoll changed the title Spring native processAot bug - composition of beans through interfaces CGLIB proxy requested at runtime for an interface that has a default method with a Bean factory method Apr 19, 2024
@snicoll snicoll changed the title CGLIB proxy requested at runtime for an interface that has a default method with a Bean factory method Configuration class with Bean factory method on an interface generates wrong target with AOT Apr 19, 2024
@snicoll
Copy link
Member

snicoll commented Apr 22, 2024

I've made good progress on this one with AOT now recognizing the default method from the parent and generating the appropriate code for the target class, e.g.

/**
 * Bean definitions for {@link BeanConfig}.
 */
@Generated
public class BeanConfig__BeanDefinitions {
  /**
   * Get the bean definition for 'beanConfig'.
   */
  public static BeanDefinition getBeanConfigBeanDefinition() {
    RootBeanDefinition beanDefinition = new RootBeanDefinition(BeanConfig.class);
    beanDefinition.setTargetType(BeanConfig.class);
    ConfigurationClassUtils.initializeConfigurationClass(BeanConfig.class);
    beanDefinition.setInstanceSupplier(BeanConfig$$SpringCGLIB$$0::new);
    return beanDefinition;
  }

  /**
   * Get the bean instance supplier for 'createACar'.
   */
  private static BeanInstanceSupplier<Car> getCreateACarInstanceSupplier() {
    return BeanInstanceSupplier.<Car>forFactoryMethod(BeanConfig.class, "createACar")
            .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(BeanConfig.class).createACar());
  }

  /**
   * Get the bean definition for 'createACar'.
   */
  public static BeanDefinition getCreateACarBeanDefinition() {
    RootBeanDefinition beanDefinition = new RootBeanDefinition(Car.class);
    beanDefinition.setInstanceSupplier(getCreateACarInstanceSupplier());
    return beanDefinition;
  }
}

Unfortunately, it still does not work as the core container attempts to create a CGLIB proxy for the interface. It should not and that probably deserves its own issue.

snicoll added a commit to snicoll/spring-framework that referenced this issue Apr 22, 2024
Previously, if a factory method is defined on a parent, the generated
code would blindly use the method's declaring class for both the target
of the generated code, and the signature of the method.

This commit improves the resolution by considering the factory metadata
in the BeanDefinition.

Closes spring-projectsgh-32609
@jhoeller jhoeller added the in: core Issues in core modules (aop, beans, core, context, expression) label Apr 22, 2024
@snicoll snicoll modified the milestones: 6.1.x, 6.1.7 Apr 22, 2024
@snicoll
Copy link
Member

snicoll commented Apr 22, 2024

@MartinLei that fixed the sample you've shared but please give that a try on your project when you get a chance.

@MartinLei
Copy link
Author

I checked it on the real project. It works like a charm.

Merci @snicoll

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 type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants