Autowiring does not find a suitable candidate in 6.2.3 any more. Throws an exception on startup. This used to work with all previous versions, including 6.2.2.
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.PaymentCreator<spring.Payment, spring.PaymentCreatorParameter<spring.Payment>>' available: expected single matching bean but found 2: bankTransferCreator,electronicCashPaymentCreator
Reproducer: bug.zip
Comment From: sbrannen
Thanks for raising the issue, @ML-Marco.
I have confirmed that your sample application works with 6.1.17
but fails with 6.2.3
and 6.2.4-SNAPSHOT
with the following exception.
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'spring.PaymentCreator<spring.Payment, spring.PaymentCreatorParameter<spring.Payment>>' available: expected single matching bean but found 2: bankTransferCreator,electronicCashPaymentCreator
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:218) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1644) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1555) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785) ~[spring-beans-6.2.4-SNAPSHOT.jar:6.2.4-SNAPSHOT]
... 35 more
We'll look into it.
Comment From: ML-Marco
@sbrannen It doesn't fail with 6.2.2. This might help to find the commit that broke it.
Comment From: sbrannen
@sbrannen It also fails with 6.2.2. This might help to find the commit that broke it.
I apologize: I was unclear.
In my tests, it fails against 6.2.0
through 6.2.4-SNAPSHOT
.
So, it appears that the regression was introduced during the 6.2 milestone/RC phases.
Comment From: jhoeller
It's worth noting that the algorithm actually finds two candidates rather than none, so it's a not-unique scenario where it ends up with two candidates which are considered equivalent in terms of the type match. From an initial glance at the repro project, it's not clear to me which one of the two beans is actually meant to be injected there (i.e. which one got injected with the algorithm as it was in 6.1) since both of them look like a match to me. Any insight in terms of the intended design there from your side, @ML-Marco?
Comment From: ML-Marco
@jhoeller I corrected my statement above. The reproducer does work with 6.2.2 and fails with 6.2.3.
I guess the one that was found in version 6.2.2 which is the base class (PaymentCreator
) and not one of its subclasses.
Comment From: sbrannen
@jhoeller I corrected my statement above. The reproducer does work with 6.2.2 and fails with 6.2.3.
Oops. I need to correct my above statement as well. I must have had an issue with caching or something. Here are my findings.
- 6.1.17: works
- 6.2.0: fails
- 6.2.1: works
- 6.2.2: works
- 6.2.3: fails
- 6.2.4-SNAPSHOT: fails
And when I say "works", I simply mean it did not throw a NoUniqueBeanDefinitionException
at startup.
Comment From: jhoeller
Given the actual regression in 6.2.3, I have a suspicion that it's caused by #34300 where the handling of nested variable bounds became stricter. I'll try to reproduce this in a unit test.
Comment From: jhoeller
A quick update before I resume tomorrow: This turns out to be rather puzzling. Of the 4 PaymentCreator
beans, we used to consider 3 as assignable (BankTransferCreator, ElectronicCashPaymentCreator, PaymentCreator - choosing the latter based on the bean name match) and now only consider 2 as assignable (BankTransferCreator, ElectronicCashPaymentCreator but not plain PaymentCreator with its unresolved generics anymore). However, in actual Java source code, none of those seems to be actually assignable without forcing an unchecked assignment. Something is odd about the generic declaration there with its interdependent type variables.
The good news is that I can reproduce this with plain ResolvableType.isAssignableFrom
calls. The effect is rather obvious, just the cause of the regression and - more importantly - the expected correct behavior is unclear. The following reproduces the effect above when testing against 6.2.3 versus 6.2.2. And alternatively, try to assign an instance of one of those classes to the field in Java code...
PaymentCreator<? extends Payment, PaymentCreatorParameter<? extends Payment>> paymentCreator;
@Test
void testResolvableType() throws Exception {
ResolvableType field = ResolvableType.forField(getClass().getDeclaredField("paymentCreator"));
System.out.println("BankTransferCreator: " + field.isAssignableFrom(BankTransferCreator.class));
System.out.println("DirectDebitCreator: " + field.isAssignableFrom(DirectDebitCreator.class));
System.out.println("ElectronicCashPaymentCreator: " + field.isAssignableFrom(ElectronicCashPaymentCreator.class));
System.out.println("PaymentCreator: " + field.isAssignableFrom(PaymentCreator.class));
}
Comment From: sbrannen
However, in actual Java source code, none of those seems to be actually assignable without forcing an unchecked assignment. Something is odd about the generic declaration there with its interdependent type variables.
Indeed, in Java source code only the concrete PaymentCreator
is assignable to the paymentCreator
field in TestController
(with an unchecked
warning as you mentioned).
The other 3 beans would only be assignable if the declaration of that field were changed to use ? extends PaymentCreatorParameter
in order to match on any of the subtypes of PaymentCreatorParameter
.
For example, the following 4 assignments compile for me.
PaymentCreator<? extends Payment, PaymentCreatorParameter<? extends Payment>> paymentCreator = new PaymentCreator();
PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> directDebitCreator = new DirectDebitCreator();
PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> bankTransferCreator = new BankTransferCreator();
PaymentCreator<? extends Payment, ? extends PaymentCreatorParameter<? extends Payment>> electronicCashPaymentCreator = new ElectronicCashPaymentCreator();
Comment From: jhoeller
As far as I see now, we tolerate a direct PaymentCreatorParameter
declaration versus an ? extends PaymentCreatorParameter
clause in the generic signature when it affects the bound type directly - whereas for a subclass like DirectDebitCreatorParameter
as declared in DirectDebitCreator
, we insist on the original Java language semantics of ? extends PaymentCreatorParameter
to match (which is why it is the odd one out there). This lenient matching of a bound type is a bit unorthodox but worth strategically retaining since a lot of application code potentially relies on it for many years already.
So for some reason, we lost that tolerance for the plain PaymentCreator
case in 6.2.3 which we need to restore. I'll revise this for 6.2.4, addressing that immediate regression.
Comment From: jhoeller
The revision is available in the latest 6.2.4 snapshot now. Please give it an early try if you have the chance...
Comment From: ML-Marco
Tried it, works! Thanks a lot for the quick response.
Comment From: jhoeller
Good to hear! Thanks for the immediate feedback.