This repository demonstrates a subtle issue with Spring's @Qualifier annotation behavior in parent-child application context hierarchies. The project shows how qualifier resolution can unexpectedly differ between standard contexts and hierarchical contexts.
When Spring attempts to resolve dependencies via autowiring, it follows a specific order of resolution strategies.
- Exact Qualifier Match: Spring first attempts to find a bean that matches the specified
@Qualifiervalue exactly - Bean Name Match: If no qualifier is specified, Spring looks for a bean with the same name as the field/parameter
- Type Match with @Primary: If multiple beans of the required type exist, Spring will select the one marked with
@Primary - Type Match by Name: Spring will try to match by bean type and use the field/variable name to disambiguate
- Any Type Match: If only one bean of the required type exists, Spring will use it
In a standard Spring context, when using @Qualifier, the qualifier value takes precedence over the field name. For example:
@Autowired
@Qualifier("leftBean") // This should determine which bean gets injected
TestInterface rightField; // Field name should not affect resolutionSpring should inject the bean named "leftBean" regardless of the field being named "rightField".
When using a parent-child context hierarchy, where beans are defined in the parent context and injected into beans in the child context, the qualifier resolution may not work as expected. In some cases, the field name seems to influence which bean gets injected, despite the presence of a @Qualifier annotation.
The issue can be reproduced with this simplified test:
// In parent context
@Bean({"open.left", "left"})
public TestInterface left() {
return () -> true; // Returns true
}
@Bean({"open.right", "right"})
public TestInterface right() {
return () -> false; // Returns false
}
// In child context
private static class TestClass {
@Autowired
@Qualifier("open.left") // Should inject bean that returns true
TestInterface right; // Field name suggests "right" bean
public boolean doit() {
return right.doItRight();
}
}
// Test method
@Test
public void testInterface() {
AnnotationConfigApplicationContext subCtx = new AnnotationConfigApplicationContext();
subCtx.setParent(context);
subCtx.register(SubConfiguration.class);
subCtx.refresh();
// This assertion may fail - returns false instead of true
Assertions.assertTrue(subCtx.getBean(TestClass.class).doit());
}This project contains following tests:
- TestBeanQualifiers.java - The usage of bean qualifiers in the current application context
- TestBeanQualifiersInSubContextCorrectFieldName.java - The usage of bean qualifiers in a sub application context with proper field name
- TestBeanQualifiersInSubContextWrongFieldName.java - The usage of bean qualifiers in a sub application context with intentional wrong field name
- Spring Boot 3.4.4
- Java 11 or higher
- Maven 3.6 or higher
To run the tests and see the different behaviors:
./mvnw test