Spring inference construction method (medium)

Keywords: Java Spring Back-end

stay Inferential construction method (I) It introduces how to obtain the candidate construction method through determineconstructors frombeanpostprocessors(). After obtaining the construction method, the next step is to infer which construction method to use and instantiate a bean according to the construction method

If the candidate construction method is not empty, or the injection method is specified as construction injection, or the parameter value of the construction method is specified through BeanDefinition, or the parameter value of the construction method is passed in when obtaining the instance through getBean(), construction injection is required for these cases, and autowireConstructor() is called to complete instantiation

If the above conditions are not satisfied, the parameterless construction method will be used for instantiation

Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);

// If the construction method is inferred, you need to assign a value to the construction method, that is, assign a value to the construction method parameters, that is, construct method injection
// If there is no inferred construction method, but autowiremode is AUTOWIRE_CONSTRUCTOR, you may also need to assign a value to the constructor, because it is uncertain whether to use the constructor without parameters or with parameters
// If the parameter value of the construction method is specified through BeanDefinition, the construction method must be injected
// If the constructor parameter value is passed in when calling getBean, it must be constructor injection
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
    mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
    return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
    return autowireConstructor(beanName, mbd, ctors, null);
}

// No special handling: simply use no-arg constructor.
// If the above conditions do not match, the nonparametric construction method is directly used
return instantiateBean(beanName, mbd);

Briefly introduce the second and third situations above

  • Specify injection type

    Specify the injection type as construction injection through xml

    public class UserService {
    
        OrderService orderService;
    
        public UserService(OrderService orderService){
    
        }
    }
    
    <bean id="orderService" class="com.lizhi.service.OrderService"/>
    <bean id="userService" class="com.lizhi.service.UserService" autowire="constructor"/>
    

    Specify construct injection through BeanDefinition

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    //Specified as construction injection through beandefinition
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    context.registerBeanDefinition("userService",beanDefinition);
    
  • BeanDefinition specifies the value of the constructor parameter

    Specify parameter values for the construction method

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    //Specifies the value of the construction method parameter
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new OrderService());
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    context.registerBeanDefinition("userService",beanDefinition);
    

    Specifies the parameter value for the specified parameter of the construction method according to the index

    Specify a parameter value for the first parameter of the construction method

    beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,new OrderService());
    

1, Preliminarily determine the construction method and parameters

The constructorToUse variable is used to store the construction method to be used. The argsToUse variable is used to store the parameter values of the construction method to be used. The argumentholder encapsulates the parameter values of the construction method

First, judge whether the constructor parameter is specified when calling the getBean() method. If so, the specified value will be used as the parameter value of the constructor first

If it is not specified, judge whether the constructor parameter value is cached in the BeanDefinition

public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
      @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {
   Constructor<?> constructorToUse = null;
   ArgumentsHolder argsHolderToUse = null;
   Object[] argsToUse = null;

   // If getBean() passes args, the input parameters to be used in the construction method can be determined directly
   if (explicitArgs != null) {
      argsToUse = explicitArgs;
   }
   else {
      Object[] argsToResolve = null;
      synchronized (mbd.constructorArgumentLock) {
         constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
         if (constructorToUse != null && mbd.constructorArgumentsResolved) {
            // Found a cached constructor...
            argsToUse = mbd.resolvedConstructorArguments;
         }
      }
   }
}

2, Inferring construction methods and method parameters

If there is no constructor in the cache or no available constructor parameters are available, you need to infer and obtain them by yourself

2.1 inference uses the default parameterless construction method

First obtain the candidate construction method array inferred above. If it is null, there are two cases: 1. There is only one default parameterless construction method. 2. There are multiple parameterless construction methods. Only these two cases will return null

If there is no candidate construction method, obtain the construction method directly according to beanClass

If there is only one construction method, and the parameters of the construction method are not specified when calling getBean(), and the parameter value of the construction method is not specified in BeanDefinition, the default parameterless construction method is used for instantiation, and the construction method and parameters (empty array) are cached to prevent unnecessary construction inference later

// If the constructor to be used is not determined, or the constructor is determined, but the parameter value to be passed in is not determined
if (constructorToUse == null || argsToUse == null) {
   // Take specified constructors, if any.
   // If no constructor is specified, all constructor candidates in beanClass are obtained
   Constructor<?>[] candidates = chosenCtors;
   if (candidates == null) {
      Class<?> beanClass = mbd.getBeanClass();
      candidates = (mbd.isNonPublicAccessAllowed() ?
               beanClass.getDeclaredConstructors() : beanClass.getConstructors());
   }
   // If there is only one candidate construction method, and the parameter value of the construction method to be used is not specified, and the construction method is parameterless, it can be instantiated directly with this parameterless construction method
   if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
      Constructor<?> uniqueCandidate = candidates[0];
      if (uniqueCandidate.getParameterCount() == 0) {
         synchronized (mbd.constructorArgumentLock) {
            mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
            mbd.constructorArgumentsResolved = true;
            mbd.resolvedConstructorArguments = EMPTY_ARGS;
         }
         bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS));
         return bw;
      }
   }
    ......
}

2.2 record the minimum number of parameters of the construction method

First, judge whether structure injection is required. If the candidate construction method is not empty or the injection method is specified as structure injection, structure injection is required

The candidate construction method is not empty, including two cases: 1. There is a construction method with @ Autowired; 2. There is only one multi parameter construction method. For both cases, construction injection is required

Then record the minimum number of parameters required to construct the method. If the parameters of the construction method are specified when getBean() is called, the minimum number of parameters is the number of parameters specified in getBean(); If the construction method parameters are specified by BeanDefinition, the minimum number of parameters is calculated by specifying the maximum index position. As mentioned earlier in the article, BeanDefinition can specify the parameter value of the index position. If the specified index value is 2, it means that the construction method to be used contains at least 3 parameter values

Recording the minimum number of parameters of a construction method is mainly used to filter construction methods. If the number of parameters of a construction method is specified as 2, the construction method with only one parameter cannot be used. Only the construction method with the number of parameters greater than or equal to 2 can be used

// Need to resolve the constructor.
boolean autowiring = (chosenCtors != null ||
                      mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
ConstructorArgumentValues resolvedValues = null;

// Determine the minimum number of parameters of the construction method to be selected, and then judge the number of parameters of the candidate construction method. If it is less than minNrOfArgs, pass it directly
int minNrOfArgs;
if (explicitArgs != null) {
    // If the parameter value of the construction method is directly passed, the number of parameters of the construction method used must not be less than
    minNrOfArgs = explicitArgs.length;
}
else {
    // If the parameter value of the construction method is passed through BeanDefinition, it may be specified through subscripts, such as the value at position 0 and the value at position 2. Although only 2 values are specified, the number of parameters of the construction method must be at least 3
    ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
    resolvedValues = new ConstructorArgumentValues();
    // Processing RuntimeBeanReference
    minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}

2.3 ranking of construction methods

After the constructor is obtained, it is sorted according to the public type and the number of parameters, that is, the constructor with the public type and the most parameters is used first for instantiation

// The candidate construction methods are sorted. Public methods rank first. When they are all public, the more the number of parameters, the higher the number
AutowireUtils.sortConstructors(candidates);
public static void sortConstructors(Constructor<?>[] constructors) {
   Arrays.sort(constructors, EXECUTABLE_COMPARATOR);
}
public static final Comparator<Executable> EXECUTABLE_COMPARATOR = (e1, e2) -> {
   int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers()));
   return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());
};

2.4 filter by traversal construction method

The following logic obtains the parameter value and calculates the score of the construction method in the traversal construction method

If the available construction method has been found, it is necessary to compare whether the number of parameters of the previous construction method is more than that of the current construction method in the next traversal. If it is more than the current construction method, it is considered that the last available construction method is the most appropriate

If the number of parameters of the current construction method is less than the required minimum number of parameters, the construction method is directly filtered and unavailable

// Each construction method is traversed and filtered
for (Constructor<?> candidate : candidates) {
   // Number of parameters
   int parameterCount = candidate.getParameterCount();

   // During this traversal, the construction method and input parameter object to be used have been selected before, and the number of input parameter objects is more than the number of parameters of the construction method currently traversed, so there is no need to traverse and exit the loop
   if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
      // Already found greedy constructor that can be satisfied ->
      // do not look any further, there are only less greedy constructors left.
      break;
   }
   // If the number of parameters is less than the required number of parameters, the next one will be traversed. Here, we consider the existence of both public and non-public construction methods
   if (parameterCount < minNrOfArgs) {
      continue;
   }
    ......
}

2.5 finding construction method parameters

The resolvedValues variable records the parameter value of the construction method specified by BeanDefinition. If BeanDefinition is not specified and getBean() does not specify the parameter value, then resolvedValues is an empty ConstructorArgumentValues object, but not null

@ConstructorProperties can be used to specify the parameter name of the construction method. If this annotation is added, the specified name will be used as the parameter name; If the annotation is not added, the parameter name when constructing the method definition is obtained through reflection technology

Then call createArgumentArray() to get the parameters. If getBean() specifies the parameter value, determine whether the number of the parameters of the current ergodic construction method is the same as the number of the specified parameters. If the number is the same, even if we find the parameters of the current construction method, then we will decide whether the construction method is the best.

ArgumentsHolder argsHolder;
Class<?>[] paramTypes = candidate.getParameterTypes();
// The constructor parameter value is not specified through getBean()
if (resolvedValues != null) {
   try {
      // If @ ConstructorProperties is used on the constructor, the defined value is directly taken as the parameter name of the constructor
      String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);

      // Get constructor parameter name
      if (paramNames == null) {
         ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
         if (pnd != null) {
            paramNames = pnd.getParameterNames(candidate);
         }
      }

      // Find the corresponding bean object according to the parameter type and parameter name
      argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
            getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
   }
   catch (UnsatisfiedDependencyException ex) {
      // The constructor currently being traversed cannot find an available input parameter object. Record it
      if (logger.isTraceEnabled()) {
         logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
      }
      // Swallow and try next constructor.
      if (causes == null) {
         causes = new ArrayDeque<>(1);
      }
      causes.add(ex);
      continue;
   }
}
else {
   // Explicit arguments given -> arguments length must match exactly.
   // The parameter value of the construction method is not specified through the BeanDefinition, but the parameter value is passed in when calling the getBean method, which means that the construction method with the corresponding number of parameters can only be used
   if (parameterCount != explicitArgs.length) {
      continue;
   }
   // There is no need to look up the bean object in BeanFactory. It already exists, and the construction method currently being traversed is the available construction method
   argsHolder = new ArgumentsHolder(explicitArgs);
}

In the createArgumentArray() method, it will traverse the parameter type and find the parameter value

The parameter values specified in BeanDefinition are stored in the method input parameter resolvedValues. resolvedValues contains the parameter value set specified by index and the general parameter set. If the parameter values are specified in resolvedValues, they will be found first from indexedargumentvalues (parameter map specifying index) and then from genericargumentvalues (general parameter set), If it is found, then judge whether type conversion and type conversion are required

If no parameter value is specified in resolvedValues, determine whether injection is required through the autowiring input parameter. Only when injection is required, Spring will find the injection value for the construction method; If no injection is required, which means that the parameter value of the construction method cannot be found, the exception is thrown directly

If injection is needed, resolveautowiredaregument() will be called. This method will call the core resolveDependency() to find the Bean instance according to the type and name. When dependency injection is used, resolveDependency() is also used to find the injection value for the attribute. For this method, see Dependency injection (Part 2) Detailed introduction

private ArgumentsHolder createArgumentArray(
    String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
    BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
    boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {

    ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
    Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
    Set<String> autowiredBeanNames = new LinkedHashSet<>(4);

    // Parameter type of traversal constructor
    for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {

        // Get the specific parameter type and parameter name
        Class<?> paramType = paramTypes[paramIndex];
        String paramName = (paramNames != null ? paramNames[paramIndex] : "");

        // Try to find matching constructor argument value, either indexed or generic.
        ConstructorArgumentValues.ValueHolder valueHolder = null;
        // If the parameter value of the construction method is specified in the BeanDefinition, the specific object will be obtained
        if (resolvedValues != null) {
            valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
            if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
                valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
            }
        }
        if (valueHolder != null) {
            // We found a potential match - let's give it a try.
            // Do not consider the same value definition multiple times!
            usedValueHolders.add(valueHolder);
            //The omitted code mainly deals with type conversion
            ......

            // The parameter value corresponding to the currently traversed parameter
            args.arguments[paramIndex] = convertedValue;
            args.rawArguments[paramIndex] = originalValue;
        }
        else {
            MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
            // No explicit match found: we're either supposed to autowire or
            // have to fail creating an argument array for the given constructor.
            if (!autowiring) {
                throw new UnsatisfiedDependencyException(
                    mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
                    "Ambiguous argument values for parameter of type [" + paramType.getName() +
                    "] - did you specify the correct bean references as arguments?");
            }
            try {
                // Match Bean objects from BeanFactory based on method parameter types and names
                Object autowiredArgument = resolveAutowiredArgument(
                    methodParam, beanName, autowiredBeanNames, converter, fallback);

                // The parameter value corresponding to the currently traversed parameter
                args.rawArguments[paramIndex] = autowiredArgument;
                args.arguments[paramIndex] = autowiredArgument;
                args.preparedArguments[paramIndex] = autowiredArgumentMarker;
                args.resolveNecessary = true;
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(
                    mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
            }
        }
    }

    return args;
}

2.6 finding the best construction method

The parameter value of the construction method has been found earlier, and then each construction method will be scored. The construction method with the lowest score is considered to be the most matching construction method

The scoring is divided into loose mode and strict mode:

In strict mode, if the final parameter does not match the parameter type (after type conversion), integer.max is returned directly_ Value, indicating very mismatch; If the original parameter does not match the current type, integer.max is returned_ VALUE - 512; Integer.max is returned if both the final and original parameters match the parameter type_ VALUE - 1024

In loose mode, if the final value does not match the parameter type, integer.max is returned directly_ VALUE; If the value inherits the parent class and implements the interface, the score will be calculated according to the value type and parameter type. For each additional layer of parent class, the score will be increased by 2. For interfaces, the score will be increased by 1

The smaller the calculated score, the more matching the construction method. If the score is the same, record it and put it into the ambiguousConstructors cache

int minTypeDiffWeight = Integer.MAX_VALUE;
// The input parameter objects required by the current traversal construction method are found. A matching value is calculated according to the parameter type and the found parameter object. The smaller the value, the more matching
// Lenient indicates loose mode
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this constructor if it represents the closest match.
// Smaller values match
if (typeDiffWeight < minTypeDiffWeight) {
    constructorToUse = candidate;
    argsHolderToUse = argsHolder;
    argsToUse = argsHolder.arguments;
    minTypeDiffWeight = typeDiffWeight;
    ambiguousConstructors = null;
}
// When the values are equal, record the construction method with the same matching value
else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
    if (ambiguousConstructors == null) {
        ambiguousConstructors = new LinkedHashSet<>();
        ambiguousConstructors.add(constructorToUse);
    }
    ambiguousConstructors.add(candidate);
}

Calculation rules in loose mode: for example, if the parameter type is A, and A inherits from class B, class B inherits from class C, and A implements interface D

Object[] objects = new Object[]{new A()};

// 0
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{A.class}, objects));

// 2
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{B.class}, objects));

// 4
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{C.class}, objects));

// 1
System.out.println(MethodInvoker.getTypeDifferenceWeight(new Class[]{D.class}, objects));

2.7 generate instances

If no available constructor is found in the previous operation, or multiple constructors with the same matching degree are found, and you don't know which one to use, exceptions will be thrown in both cases

If the getBean() call does not pass in parameters and finds the construction method and the input parameter object to be used, cache it, and then use the construction method to generate an instance and return it

// If no constructor is available, take the last exception of the record and throw it
if (constructorToUse == null) {
   throw new BeanCreationException(mbd.getResourceDescription(), beanName,
         "Could not resolve matching constructor on bean class [" + mbd.getBeanClassName() + "] " +
         "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)");
}
// If there are construction methods available, but there are multiple
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
   throw new BeanCreationException(mbd.getResourceDescription(), beanName,
         "Ambiguous constructor matches found on bean class [" + mbd.getBeanClassName() + "] " +
         "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
         ambiguousConstructors);
}

// If no parameters are passed in through the getBean method, and the constructor and the input parameter object to be used are found, the cache
if (explicitArgs == null && argsHolderToUse != null) {
   argsHolderToUse.storeCache(mbd, constructorToUse);
}

bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;

Posted by duvys on Wed, 27 Oct 2021 01:27:37 -0700