spring-projects/spring-boot

Use of @EntityScan causes AOT instance supplier code generation error

trcoelho opened this issue ยท 12 comments

Hello!

After updating to Spring boot 3.0.3, native images are showing up the following exception:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.3)

2023-02-24T10:15:53.005-03:00  INFO 4316 --- [           main] c.example.application.ApplicationConfig  : Starting ApplicationConfig using Java 17.0.6 with PID 4316 (C:\Users\tcoelho1\Desktop\spring-boot-3.0.3\target\classes started by tcoelho1 in C:\Users\tcoelho1\Desktop\spring-boot-3.0.3)
2023-02-24T10:15:53.012-03:00  INFO 4316 --- [           main] c.example.application.ApplicationConfig  : No active profile set, falling back to 1 default profile: "default"
2023-02-24T10:15:54.469-03:00  INFO 4316 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-02-24T10:15:54.620-03:00  INFO 4316 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 127 ms. Found 1 JPA repository interfaces.
Exception in thread "main" java.lang.IllegalArgumentException: Code generation is not supported for bean definitions declaring an instance supplier callback : Root bean: class [org.springframework.boot.autoconfigure.domain.EntityScanPackages]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
        at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.<init>(BeanDefinitionMethodGenerator.java:82)
        at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:100)
        at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:115)
        at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:48)
        at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:36)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.getContributions(BeanFactoryInitializationAotContributions.java:67)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:49)
        at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:44)
        at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:58)
        at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
        at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
        at org.springframework.context.aot.ContextAotProcessor.performAotProcessing(ContextAotProcessor.java:106)
        at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:84)
        at org.springframework.context.aot.ContextAotProcessor.doProcess(ContextAotProcessor.java:49)
        at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82)
        at org.springframework.boot.SpringApplicationAotProcessor.main(SpringApplicationAotProcessor.java:80)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.942 s
[INFO] Finished at: 2023-02-24T10:15:57-03:00
[INFO] ------------------------------------------------------------------------

When run mvn -Pnative spring-boot:build-image.

Curiously it did not happen on Spring boot 3.0.2.

I've attached these two projects that happens what I explained.

spring-boot-examples.zip

Am I missing some configuration or is this a bug?

Thanks in advance.

This is an intentional change in Spring Framework 6.0.5, which is used in Spring Boot 3.0.3. See spring-projects/spring-framework#29556 for the details and spring-projects/spring-framework#29555 for discussion on potential future changes in Framework.

Hi @scottfrederick , thanks for quickly reply.

So that means we are unable to build native images when application uses Spring Data on 3.0.3?

Do you know if It will be "fixed" in future Spring boot/Spring releases?

Thank you.

I wonder if we've missed replacing this code:

@Override
public Supplier<?> getInstanceSupplier() {
return () -> new EntityScanPackages(StringUtils.toStringArray(this.packageNames));
}

That said, our JPA smoke test is passing at the moment. Perhaps the use of custom packages in the sample is the cause?

Thanks for the sample @trcoelho, I should have looked more closely at it before closing the issue. The presence of the @EntityScan annotation triggers this error. This wasn't caught by the JPA smoke test that Andy mentioned, which we use to verify AOT behavior across Spring projects.

Thinking about this some more, I don't think @EntityScan actually does anything in a native image.

In Spring Framework 6.0.4, custom instance suppliers were silently ignored. This means that in Boot 3.0.2, the entity scan packages are empty. That doesn't actually matter because the persistent types have been found at build time and recorded in the PersistenceManagedTypes bean definition:

    /**
     * Get the bean instance for 'persistenceManagedTypes'.
     */
    private static PersistenceManagedTypes getPersistenceManagedTypesInstance() {
      List<String> managedClassNames = List.of("com.example.entity.Authentication", "com.example.entity.IdentifiableEntity");
      List<String> managedPackages = List.of();
      return PersistenceManagedTypes.of(managedClassNames, managedPackages);
    }

    /**
     * Get the bean definition for 'persistenceManagedTypes'
     */
    public static BeanDefinition getPersistenceManagedTypesBeanDefinition() {
      Class<?> beanType = PersistenceManagedTypes.class;
      RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
      beanDefinition.setPrimary(true);
      beanDefinition.setInstanceSupplier(PersistenceManagedTypesConfiguration__BeanDefinitions::getPersistenceManagedTypesInstance);
      return beanDefinition;
    }

The change in Framework 6.0.5 means that the instance supplier is no longer ignored and causes a failure instead. I think we can just exclude the bean during AOT processing.

@trcoelho You can work around the problem with Spring Boot 3.0.3 by using a BeanRegistrationExcludeFilter:

package com.example;

import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.boot.autoconfigure.domain.EntityScanPackages;

class EntityScanExcludeFilter implements BeanRegistrationExcludeFilter {

	@Override
	public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) {
		return registeredBean.getBeanClass().equals(EntityScanPackages.class);
	}

}

Such filters are registered in a file named META-INF/spring/aot.factories under the key org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter:

org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
com.example.EntityScanExcludeFilter

Thanks @wilkinsona !

Is this will be fixed on next spring boot release?

Thank you.

We hope so, yes.

Hi all.

FYI

Have tested on Spring Boot 3.0.4 and we still got same issue. :(

Thank you.

@trcoelho The issue is still open, indicating that we haven't attempted to fix this problem yet. When we have a fix the issue will be closed and assigned to a specific version milestone.

fyi: The issue is still occuring while running tests with @SpringBootTest using latest spring boot version "3.1.0"

@CasaSky Are you able to open a new issue and provide a reproducer?