Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Spring's Constructor Selection Strategy with Multiple Constructors

Tech Oct 13 11

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:

  1. 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.

  2. 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, getDeclaredConstructors is used.

  3. 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.

  4. 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 any RuntimeBeanReference dependencies within these arguments.
  5. Constructor Sorting: Candidate constructors are sorted. Public constructors are prioritized. Among public constructors, those with more parameters are generally preferred.

  6. 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 @ConstructorProperties or ParameterNameDiscoverer) with beans available in the factory. This involves creating an ArgumentsHolder.
    • 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.
  7. 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 getTypeDifferenceWeight method 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.
  8. Instantiation: Once the best constructor and its arguments are identified, Spring instantiates the bean using the instantiate method.

  9. Caching: If no explicit arguments were provided by the calller, the resolved constructor and its arguments are cached in the RootBeanDefinition for 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.class with an A instance yields a score of 0.
  • Matching B.class with an A instance yields a score of 2.
  • Matching C.class with an A instance yields a score of 4.
  • Matching D.class (an interface) with an A instance 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)
    }
}

Tags: spring

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.