Spring's Constructor Selection Strategy with Multiple Constructors
When Spring encounters a class with multiple constructors and needs to instantiate an object, it employs a sophisticated strategy to determine the most suitable constructor. This process is primarily handled by the autowireConstructor method within Spring's bean factory.
The core logic of autowireConstructor begins by checking if explicit arguments have been provided for the constructor. If so, these arguments directly dictate the constructor to be used.
If no explicit arguments are supplied, Spring proceeds to determine the constructor and its arguments. This involves several steps:
-
Cached Constructor Check: Spring first checks if a constructor has already been resolved and cached for the bean definition. If a cached constructor exists and its arguments are resolved, these are used.
-
Candidate Constructor Discovery: If no cached constructor is found or arguments are unresolved, Spring identifies all potential candidate constructors. This typically involves retrieving all public constructors of the bean class. If non-public constructors are allowed,
getDeclaredConstructorsis used. -
Single Constructor Optimization: A special case is handled for classes with a single constructor. If this constructor is parameterless and no constructor arguments are explicitly defined in the bean definition, Spring directly uses this unique constructor for instatniation.
-
Argument Resolution and Minimum Count: Spring determines the minimum number of arguments required for a constructor.
- If explicit arguments are provided, the minimum count is simply the number of explicit arguments.
- If arguments are defined in the
BeanDefinition(e.g., using indexed arguments), Spring analyzes these to establish a minimum required parameter count. It resolves anyRuntimeBeanReferencedependencies within these arguments.
-
Constructor Sorting: Candidate constructors are sorted. Public constructors are prioritized. Among public constructors, those with more parameters are generally preferred.
-
Candidate Evaluation: Spring iterates through the sorted candidate constructors. For each constructor:
- It checks if the constructor's parameter count is sufficient based on the determined minimum.
- Argument Matching:
- If explicit arguments were provided by the caller (
getBean), the constructor's parameter count must exactly match the number of explicit arguments. - If arguments are resolved from the
BeanDefinition, Spring attempts to match constructor parameters (by type and name, if available via@ConstructorPropertiesorParameterNameDiscoverer) with beans available in the factory. This involves creating anArgumentsHolder.
- If explicit arguments were provided by the caller (
- Dependency Resolution Failure: If a constructor cannot be satisfied due to missing depandencies, the exception is caught, and Spring attempts the next candidate. Any such exceptions are collected.
-
Best Match Selection: After evaluating candidates, Spring selects the constructor that represents the closest match. This closeness is determined by a
typeDifferenceWeight. A lower weight signifies a better match.- The
getTypeDifferenceWeightmethod calculates a score based on the compatibility between the expected parameter types and the provided arguments. It considers both raw argument types and potentially converted argument types, favoring matches based on raw types with a slight penalty. - If multiple constructors tie for the best match and lenient resolution is not enabled, Spring throws an
AmbiguousConstructorException.
- The
-
Instantiation: Once the best constructor and its arguments are identified, Spring instantiates the bean using the
instantiatemethod. -
Caching: If no explicit arguments were provided by the calller, the resolved constructor and its arguments are cached in the
RootBeanDefinitionfor future use, avoiding redundant resolution.
The scoring mechanism for determining the best constructor is crucial. The getTypeDifferenceWeight method calculates a numerical score representing the difference between the constructor's parameter types and the resolved arguments. A lower score indicates a more compatible match. For instance, if A extends B, which extends C, and A implements D:
- Matching
A.classwith anAinstance yields a score of 0. - Matching
B.classwith anAinstance yields a score of 2. - Matching
C.classwith anAinstance yields a score of 4. - Matching
D.class(an interface) with anAinstance yields a score of 1.
This scoring system ensures that Spring prefers direct type matches, followed by more general type or interface matches, ultimately selecting the constructor that requires the least "conversion" or "inference" to satisfy its parameters.
In summary, Spring's autowireConstructor method provides a robust mechanism for selecting the appropriate constructor when multiple options exist. It leverages constructor signature analysis, argument matching based on type and name, and a scoring system to identify the most suitable constructor for bean instantiation.
// Example of how Spring might resolve arguments and calculate type difference weights.
class C {}
class B extends C {}
class A extends B {}
interface D {}
class Example {
// Simplified representation of how weights might be calculated
public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
if (paramTypes.length != args.length) return Integer.MAX_VALUE; // Should not happen in this context
int totalWeight = 0;
for (int i = 0; i < paramTypes.length; i++) {
Class<?> paramType = paramTypes[i];
Object arg = args[i];
if (arg == null) continue; // Assume null can be assigned
Class<?> argType = arg.getClass();
if (paramType.isAssignableFrom(argType)) {
// Direct assignment or subtype is fine, low weight (e.g., 0 for direct match)
if (paramType.equals(argType)) {
totalWeight += 0;
} else {
// Subtype match, slightly higher weight (e.g., 2 for class hierarchy)
// This is a simplified representation; actual logic would traverse hierarchy
if (paramType.isInterface()) {
totalWeight += 1; // Interface match
} else {
totalWeight += 2; // Class hierarchy match
}
}
} else {
// No direct assignability, very high weight (this simplified logic doesn't cover conversions)
return Integer.MAX_VALUE;
}
}
return totalWeight;
}
public static void main(String[] args) {
Object[] objects = new Object[]{new A()};
// Matching against A.class
System.out.println("A vs A: " + getTypeDifferenceWeight(new Class[]{A.class}, objects)); // Expected: 0
// Matching against B.class
System.out.println("B vs A: " + getTypeDifferenceWeight(new Class[]{B.class}, objects)); // Expected: 2 (A is a B)
// Matching against C.class
System.out.println("C vs A: " + getTypeDifferenceWeight(new Class[]{C.class}, objects)); // Expected: 4 (A is a C)
// Matching against D.class (interface)
System.out.println("D vs A: " + getTypeDifferenceWeight(new Class[]{D.class}, objects)); // Expected: 1 (A implements D)
}
}