@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
- 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
- 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.
- 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?