Affects: spring-framework 6.1.12
We are trying to manage a fleet of similarly-typed beans to inject. We've found a surprising circumstance where the existence of a dependency on one bean ("A") will cause another bean ("B") to vary how you may inject it.
Below please find a test case showing the problem. The test case is attempting to inject @Named("B") IImpl<String> bStr
, a concrete subtype of an interface I<String>
. The subtype is desired to access test-stubbing features not present on the interface.
A different component is declaring @Named("A") I<UUID> aUuid
. These definitions seem unrelated, since both the name (A
vs B
) and type (I<String>
vs I<UUID>
) differ.
The test fails as is:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'BeanInjectTest$IImpl<java.lang.String>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@jakarta.inject.Inject(), @jakarta.inject.Named("B")}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1880)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1406)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:785)
...
- Changing injection of
@Named("B") IImpl<String>
to@Named("B") I<String>
works, but then you cannot access the subtype's features - Commenting out the
@Import(MyConfig.ConsumeA.class)
causes the injection of B to work, despite only changing the use ofA
notB
!
This behavior seems very surprising to me, and feels like a bug. Thank you for your thoughts.
@SpringJUnitConfig({
BeanInjectTest.MyConfig.class,
})
public class BeanInjectTest {
@Inject
@Named("B")
IImpl<String> bStr;
@Test
public void injectB() {
assertThat(bStr).isInstanceOf(IImpl.class);
}
@Import({
MyConfig.ConsumeA.class,
})
public static class MyConfig {
@Bean
@Named("B")
public I<String> bStr() {
return new IImpl<>();
}
@Named("A")
@Bean
public I<UUID> aUuid() {
return new IImpl<>();
}
public static class ConsumeA {
public ConsumeA(
@Named("A")
final I<UUID> aUuid) {
System.err.println("aUuid: " + aUuid);
}
}
}
public interface I<T> {}
public static class IImpl<T> implements I<T> {}
}
Comment From: sbrannen
Hi @stevenschlansker,
Thanks for raising the issue and providing the example.
I have confirmed that the test fails on Spring Framework 5.3.x through 6.1.x (with javax.inject
annotations on 5.3.x
and otherwise with jakarta.inject
annotations); however, the test passes when run against 6.2 (main
branch).
Thus, the behavior appears to be a longstanding limitation that has been addressed recently.
@jhoeller, thoughts?