spring-projects / spring-projects/spring-framework
Injecting one qualified bean disrupts the ability to inject another qualified bean by subtype
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 60.2k
- Forks
- 38.8k
- Avg merge
- 5d 2h
- Merged PRs (30d)
- 27
Description
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 ofAnotB!
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> {}
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the supplied BeanInjectTest and start tracing dependency resolution from DefaultListableBeanFactory.doResolveDependency and resolveDependency, along with AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement. The fix is complete when @Named("B") IImpl injects successfully while ConsumeA still injects @Named("A") I, and the test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100