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

Autowiring of a generic type produced by a factory bean fails after AOT processing #29385

Closed
wilkinsona opened this issue Oct 26, 2022 · 8 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

@wilkinsona
Copy link
Member

wilkinsona commented Oct 26, 2022

Affects: 6.0.0-RC2

We found this problem when testing Spring Boot's JacksonTester support but I believe it's a general AOT problem to do with factory beans that produce a generic type. Here's a small sample that reproduces the problem:

factory-bean-problem.zip

./gradlew test should result in two failures:

> Task :test FAILED

FactoryBeanProblemApplicationTests > contextLoads() FAILED
    org.springframework.beans.factory.UnsatisfiedDependencyException at AutowiredAnnotationBeanPostProcessor.java:744
        Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException at DefaultListableBeanFactory.java:1782

MinimalReproductionTests > aotConfig() FAILED
    org.springframework.beans.factory.UnsatisfiedDependencyException at MinimalReproductionTests.java:46
        Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException at MinimalReproductionTests.java:46

The first reproduces the problem that we've seen in Spring Boot. If you edit build.gradle to comment out the setting of the spring.aot.enabled system property and run ./gradlew test again, contextLoads() should pass. The second is an attempt to reproduce the problem in a minimal (and slightly simplified) manner. It doesn't involve Boot or the Test Framework at all.

From what I have seen in the debugger, the behavior diverges in GenericTypeAwareAutowireCandidateResolver.checkGenericTypeMatch(BeanDefinitionHolder, DependencyDescriptor). In the Java config case, the target type is determined via a BeanFactory.getType call:

if (targetType == null) {
// Regular case: straight bean instance, with BeanFactory available.
if (this.beanFactory != null) {
Class<?> beanType = this.beanFactory.getType(bdHolder.getBeanName());

This is sufficient for the result of the check to be true and for autowiring to succeed.

In the AOT-processed case, the target type is available from the bean definition:

As a result, the getType call on the bean factory never happens. The result of the check is false and autowiring fails.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 26, 2022
@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) theme: aot An issue related to Ahead-of-time processing labels Oct 26, 2022
@sbrannen sbrannen added this to the Triage Queue milestone Oct 26, 2022
@sdeleuze
Copy link
Contributor

sdeleuze commented Nov 3, 2022

I tentatively add this one for 6.0 GA since that breaking some propular use cases.

@sdeleuze sdeleuze modified the milestones: Triage Queue, 6.0.0 Nov 3, 2022
@sdeleuze sdeleuze removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 3, 2022
@jhoeller jhoeller added the type: bug A general bug label Nov 3, 2022
@jhoeller jhoeller self-assigned this Nov 8, 2022
@jhoeller jhoeller modified the milestones: 6.0.0, 6.0.0-RC4 Nov 8, 2022
@wilkinsona
Copy link
Member Author

wilkinsona commented Nov 8, 2022

Things have improved, but the problem isn't fully resolved. While the slightly simplified reproducer now passes, the other test in the sample still fails:

> Task :test FAILED

FactoryBeanProblemApplicationTests > contextLoads() FAILED
    org.springframework.beans.factory.UnsatisfiedDependencyException at AutowiredAnnotationBeanPostProcessor.java:744
        Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException at DefaultListableBeanFactory.java:1812

3 tests completed, 1 failed

FAILURE: Build failed with an exception.

As before, this test passes when AOT processing is not enabled.

@wilkinsona
Copy link
Member Author

With the recent changes, ResolvableType.isAssignableFrom(ResolvableType, Map<Type, Type>) is now called with this being org.springframework.boot.test.json.JacksonTester<com.example.demo.Request> and other being org.springframework.boot.test.json.JacksonTester<java.lang.Object>. false is returned as the generics do not align as com.example.demo.Request is not assignable from java.lang.Object.

@wilkinsona
Copy link
Member Author

This failure to match the generics can be reproduced by changing the type of the @Autowired field in Consumer to JacksonTester<Request>:

static class Consumer {

	@Autowired
	JacksonTester<Request> tester;

}

This then fails as com.example.demo.MinimalReproductionTests$Request is not assignable from java.lang.Object.

@jhoeller
Copy link
Contributor

jhoeller commented Nov 9, 2022

Thanks, Andy. This is probably not related to the original FactoryBean problem anymore but rather to an AOT difference with our fallback matching for incomplete generics (we had similar reports elsewhere). Since this is the last known AOT problem for GA, I'm trying to sort this out for good today.

@jhoeller
Copy link
Contributor

jhoeller commented Nov 9, 2022

Andy, with the injection point changed as above, if the AOT-determined target type is then also set to FactoryBean<JacksonTester<Request>>, your revised test passes fine. So this is literally about JacksonTester<Object> not being assignable to JacksonTester<Request> now - a different problem where our lenient fallback matching for generics only kicks in if no target type has been pre-determined.

I do wonder why we keep having such non-matching generics to begin with. In an ideal world, we would not need the lenient fallback match at all anymore. That said, I'm currently looking into how to let it kick in even for a pre-determined target type.

@jhoeller
Copy link
Contributor

jhoeller commented Nov 9, 2022

I got an arrangement for this now where for pre-determined FactoryBean types, we only take the resolved Class in a fallback match, which is equivalent to how lazy type determination for FactoryBeans proceeds in a fallback scenario. I'll push this shortly.

@jhoeller
Copy link
Contributor

jhoeller commented Nov 9, 2022

@wilkinsona This should be addressed in the upcoming snapshot now. Please let me know whether it works for Boot...

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

5 participants