Spring instantiation -- who is my candidate

Keywords: Spring

@Service
public class HelloService {
    @Autowired(required = false)
    public HelloService(ApplicationContext applicationContext) { // 1⃣️
    }
    @Autowired(required = false)
    public HelloService(Environment environment) { // 2⃣️
    }
}

Tell me, which constructor will Spring choose to instantiate HelloService? one ⃣ ♪ or 2 ⃣ ️ ? The answer is given at the end of the paper

Spring Audition 1

The venue of the first audition is: Autowired annotation bean post processor #determinecandidateconstructors

   Constructor<?>[] rawCandidates;
   try {
     // Reflection gets all constructors
      rawCandidates = beanClass.getDeclaredConstructors();
   }
   catch (Throwable ex) {
      throw new BeanCreationException(xxxxxxxxx);
   }
	// Constructor candidate list
   List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
	// Constructor seed candidate
   Constructor<?> requiredConstructor = null;
	// Default constructor 
   Constructor<?> defaultConstructor = null;
 // This is not a kotlin language and returns null
   Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
	// Non synthetic constructor
   int nonSyntheticConstructors = 0;
   for (Constructor<?> candidate : rawCandidates) {
      if (!candidate.isSynthetic()) // Can be ignored
         nonSyntheticConstructors++;
      }
      else if (primaryConstructor != null) {
         continue;// Can be ignored
      }
		// Judge whether the constructor is modified by @ Autowire annotation
      MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
      if (ann == null) {
         Class<?> userClass = ClassUtils.getUserClass(beanClass);
         if (userClass != beanClass) {
            try {
               Constructor<?> superCtor =
                     userClass.getDeclaredConstructor(candidate.getParameterTypes());
               ann = findAutowiredAnnotation(superCtor);
            }
            catch (NoSuchMethodException ex) {
               // Simply proceed, no equivalent superclass constructor found...
            }
         }
      }
		// Modified by annotation
      if (ann != null) {
         if (requiredConstructor != null) { // There are already seed candidates for constructors that are no longer allowed to be annotated
            throw new BeanCreationException(beanName,
                  "Invalid autowire-marked constructor: " + candidate +
                  ". Found constructor with 'required' Autowired annotation already: " +
                  requiredConstructor);
         }
         boolean required = determineRequiredStatus(ann);
         if (required) { // The current constructor is a seed candidate. Similarly, no annotation modification is allowed
            if (!candidates.isEmpty()) {
               throw new BeanCreationException(beanName,
                     "Invalid autowire-marked constructors: " + candidates +
                     ". Found constructor with 'required' Autowired annotation: " +
                     candidate);
            }
           // Become a seed candidate
            requiredConstructor = candidate;
         }
        // Add to candidate list
         candidates.add(candidate);
      }
      else if (candidate.getParameterCount() == 0) {
        // Become default constructor
         defaultConstructor = candidate;
      }
   }
   if (!candidates.isEmpty()) {
     // Candidate list is not empty
      if (requiredConstructor == null) {
         if (defaultConstructor != null) {
           // If the seed candidate is null and the default constructor is not null, add the default constructor to the candidate list as a backup scheme
            candidates.add(defaultConstructor);
         }
      }
      candidateConstructors = candidates.toArray(new Constructor<?>[0]);
   }
   else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
     // If the candidate list is null and the class declares only one constructor and it has an input parameter, it is added to the candidate list
      candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
   }
  .....Omit kotlin dependent
   else {
      candidateConstructors = new Constructor<?>[0];
   }
	// Add to cache
   this.candidateConstructorsCache.put(beanClass, candidateConstructors);
........
  // The imperial concubine selection is over
return (candidateConstructors.length > 0 ? candidateConstructors : null);

If the code is inconvenient to see, here are some important variables

rawCandidates // All constructors declared by this class
// Candidate list (constructor)
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
// Seed candidate (constructor)
Constructor<?> requiredConstructor = null;
// Default constructor 
Constructor<?> defaultConstructor = null;

Conclusions drawn

  1. If there is an Autowired modified constructor and required is true, there is only one Autowired modified constructor. And only return it as a candidate result
  2. If there are one or more constructors modified by Autowired, the required must be false. If the default constructor is declared at this time, the default constructor will be added to the candidate list and the whole candidate list will be used as the candidate result.
  3. If the constructors of this class are not decorated with Autowired, and there is only one constructor in this class and it is not a parameterless constructor, it will be returned as a candidate result.

Therefore, two constructors will be returned as candidate results in HelloService at the beginning

Spring audition II

The first audition venue is: ConstructorResolver#autowireConstructor

// Champion constructor
Constructor<?> constructorToUse = null;
// Arguments to constructor
ArgumentsHolder argsHolderToUse = null;
// Sort by method modifier first, and public has the highest priority. Then sort according to the number of parameters. The more input parameters, the higher the priority
AutowireUtils.sortConstructors(candidates);
// Minimum type difference weight
int minTypeDiffWeight = Integer.MAX_VALUE;
// List of ambiguous constructors
Set<Constructor<?>> ambiguousConstructors = null;
// Exceptions during filtering
LinkedList<UnsatisfiedDependencyException> causes = null;

// candidates audition constructor
for (Constructor<?> candidate : candidates) {
  // Number of input parameters
   int parameterCount = candidate.getParameterCount();
	 // If the champion constructor has been generated and the number of input parameters of the champion constructor is greater than that of the current constructor, there is no need to continue
  // Because Spring always wants to give you the most love it can give
   if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
      break;
   }
  // minNrOfArgs is normally 0 here and can be ignored
   if (parameterCount < minNrOfArgs) {
      continue;
   }
	
  // Try to find the parameters required by the constructor from Spring. If not, do not interrupt, but continue to the next constructor
   ArgumentsHolder argsHolder;
   Class<?>[] paramTypes = candidate.getParameterTypes();
   if (resolvedValues != null) {
      try {
         String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
         if (paramNames == null) {
            ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
            if (pnd != null) {
               paramNames = pnd.getParameterNames(candidate);
            }
         }
         argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
               getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
      }
      catch (UnsatisfiedDependencyException ex) {
         // Swallow and try next constructor.
         if (causes == null) {
            causes = new LinkedList<>();
         }
        // Save exception, dependent constructor parameter not found in Spring
         causes.add(ex);
         continue;
      }
   }
   else {
,      if (parameterCount != explicitArgs.length) {
         continue;
      }
      argsHolder = new ArgumentsHolder(explicitArgs);
   }
// Successfully find constructor parameters from Spring and compare the types of objects and parameters found here (type difference weight)
   int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
         argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
   // Which constructor has less type difference can become the champion constructor
   if (typeDiffWeight < minTypeDiffWeight) {
      constructorToUse = candidate;
      argsHolderToUse = argsHolder;
      argsToUse = argsHolder.arguments;
      minTypeDiffWeight = typeDiffWeight;
      ambiguousConstructors = null;
   }
   else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
			.... Add to the list of ambiguous constructors
      ambiguousConstructors.add(candidate);
   }
}

if (constructorToUse == null) {
  // The stream is selected and there is none
}
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
  // Because the loose pattern used by the constructor to create the bean, mbd.isLenientConstructorResolution(), has always been true
  // Therefore, even if the ambiguous constructor list appears halfway, no exception will be thrown
   throw new BeanCreationException(xxx);
}
// Create bean s using constructorToUse, argsToUse
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));

If the code is inconvenient to see, here are some important variables

// Champion constructor
Constructor<?> constructorToUse = null;
// Arguments to constructor
ArgumentsHolder argsHolderToUse = null;
// Sort by method modifier first, and public has the highest priority. Then sort according to the number of parameters. The more input parameters, the higher the priority
AutowireUtils.sortConstructors(candidates);
// Minimum type difference weight
int minTypeDiffWeight = Integer.MAX_VALUE;
// List of ambiguous constructors
Set<Constructor<?>> ambiguousConstructors = null;

Back to the question at the beginning of our article, there is no doubt that ApplicationContext and Environment can be found from BeanFactory, so its difference weight becomes the key to choose which constructor.

Because the constructor instantiates the bean, it adopts the loose mode, that is, mbd.isLenientConstructorResolution() returns true. If it is a configuration class (that is, FactoryMethod), it is a strict mode, that is, it returns false.

int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));

The parameters after the arguments and rawArguments conversion are the same as the original parameters. We think they are the same in most cases

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
   int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
   int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
   return Math.min(rawTypeDiffWeight, typeDiffWeight);
}

The smaller the weight of type difference, the higher the priority, that is, it can become the champion constructor.

public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
   int result = 0;
   for (int i = 0; i < paramTypes.length; i++) {
      if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
         return Integer.MAX_VALUE;
      }
      if (args[i] != null) {
         Class<?> paramType = paramTypes[i];
         Class<?> superClass = args[i].getClass().getSuperclass();
         while (superClass != null) {
            if (paramType.equals(superClass)) {
               result = result + 2;
               superClass = null;
            }
            else if (ClassUtils.isAssignable(paramType, superClass)) {
               result = result + 2;
               superClass = superClass.getSuperclass();
            }
            else {
               superClass = null;
            }
         }
         if (paramType.isInterface()) {
            result = result + 1;
         }
      }
   }
   return result;
}

Let's go back to the question of the article

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
   int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
   int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
   return Math.min(rawTypeDiffWeight, typeDiffWeight);
}

The type of ApplicationContext found from Spring is annotationconfigservletwebserver ApplicationContext

result = 2 + 2 + 2 + 2 + 1 = 9. The final returned result is 9 - 1024 = - 1015

The parent class DefaultResourceLoader of AbstractApplicationContext is not a subclass or implementation of ApplicationContext, so it terminates here

The type of Environment found from Spring is standardservlet Environment

result = 2 + 2 + 1 = 5. The final returned result is 5 - 1024 = - 1019

The parent class of AbstractEnvironment is Object and does not implement the Environment interface, so it terminates here.

So you know choose 1 ⃣ ♪ or 2 ⃣ Are you ready?

https://mp.weixin.qq.com/s/lCEXLQS1hs9W5xCjbL2DCg

Posted by gabe on Sat, 04 Dec 2021 15:48:02 -0800