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:
./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:
https://github.com/spring-projects/spring-framework/blob/997dd3ee65b0e30b396fa06db75eb6a80b3809bc/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java#L107-L110
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:
https://github.com/spring-projects/spring-framework/blob/997dd3ee65b0e30b396fa06db75eb6a80b3809bc/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java#L90
As a result, the getType
call on the bean factory never happens. The result of the check is false and autowiring fails.
Comment From: sdeleuze
I tentatively add this one for 6.0 GA since that breaking some propular use cases.
Comment From: wilkinsona
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.
Comment From: wilkinsona
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
.
Comment From: wilkinsona
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
.
Comment From: jhoeller
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.
Comment From: jhoeller
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.
Comment From: jhoeller
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.
Comment From: jhoeller
@wilkinsona This should be addressed in the upcoming snapshot now. Please let me know whether it works for Boot...