Loading...
墨滴

CoderLi

2021/12/02  阅读:63  主题:前端之巅同款

Spring 实例化--谁是我的候选人

微信公众号_CoderLi
微信公众号_CoderLi
@Service
public class HelloService {
    @Autowired(required = false)
    public HelloService(ApplicationContext applicationContext) // 1⃣️
    }
    @Autowired(required = false)
    public HelloService(Environment environment) // 2⃣️
    }
}

快告诉我、Spring 将选择哪个构造函数用来实例化 HelloService ? 1⃣️ 还是 2⃣️ ?文末给出答案

Spring 海选一

第一场海选的场地为 : AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors

   Constructor<?>[] rawCandidates;
   try {
     // 反射获取所有构造函数
      rawCandidates = beanClass.getDeclaredConstructors();
   }
   catch (Throwable ex) {
      throw new BeanCreationException(xxxxxxxxx);
   }
 // 构造函数候选列表
   List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
 // 构造函数种子候选
   Constructor<?> requiredConstructor = null;
 // 默认构造函数
   Constructor<?> defaultConstructor = null;
 // 这里非 kotlin 语言、返回 null
   Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
 // 非合成构造函数
   int nonSyntheticConstructors = 0;
   for (Constructor<?> candidate : rawCandidates) {
      if (!candidate.isSynthetic()) // 可以忽略
         nonSyntheticConstructors++;
      }
      else if (primaryConstructor != null) {
         continue;// 可以忽略
      }
  // 判断该构造函数是否被 @Autowire 注解修饰
      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...
            }
         }
      }
  // 被注解修饰
      if (ann != null) {
         if (requiredConstructor != null) { // 已经有种子候选了、不允许再有注解修饰的构造函数
            throw new BeanCreationException(beanName,
                  "Invalid autowire-marked constructor: " + candidate +
                  ". Found constructor with 'required' Autowired annotation already: " +
                  requiredConstructor);
         }
         boolean required = determineRequiredStatus(ann);
         if (required) { // 当前构造函数为种子候选、同理不允许再有注解修饰的构造函数
            if (!candidates.isEmpty()) {
               throw new BeanCreationException(beanName,
                     "Invalid autowire-marked constructors: " + candidates +
                     ". Found constructor with 'required' Autowired annotation: " +
                     candidate);
            }
           // 成为种子候选
            requiredConstructor = candidate;
         }
        // 加入到候选列表中
         candidates.add(candidate);
      }
      else if (candidate.getParameterCount() == 0) {
        // 成为默认构造函数
         defaultConstructor = candidate;
      }
   }
   if (!candidates.isEmpty()) {
     // 候选列表不为空
      if (requiredConstructor == null) {
         if (defaultConstructor != null) {
           // 如果种子候选为 null 、并且默认构造函数不为 null、将默认构造函数加入到候选列表中作为后备方案
            candidates.add(defaultConstructor);
         }
      }
      candidateConstructors = candidates.toArray(new Constructor<?>[0]);
   }
   else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
     // 如果候选列表为 null 并且该类只申明了一个构造函数且它存在入参、那么将它加入到候选列表中
      candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
   }
  .....省略掉 kotlin 相关的
   else {
      candidateConstructors = new Constructor<?>[0];
   }
 // 加入缓存中
   this.candidateConstructorsCache.put(beanClass, candidateConstructors);
........
  // 选妃结束
return (candidateConstructors.length > 0 ? candidateConstructors : null);

代码不方便看的话、这里列一下几个重要的变量

rawCandidates // 该类声明的所有构造函数
// 候选列表(构造函数)
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
// 种子候选(构造函数)
Constructor<?> requiredConstructor = null;
// 默认构造函数
Constructor<?> defaultConstructor = null;

得出的结论

  1. 如果存在 Autowired 修饰的构造函数、且 required 为 true 、那么有且仅有一个被 Autowired 修饰的构造函数。并且只返回其作为候选结果
  2. 如果存在一个或多个被 Autowired 修饰的构造函数、required 肯定都是 false的、这个时候如果声明了默认构造函数、则默认构造函数会加入到候选列表中、整个候选列表作为候选结果。
  3. 如果该类的构造函数都没有使用 Autowired 修饰、并且该类只存在一个构造函数、并且不是无参构造函数、那么则将其作为候选结果返回。

所以开篇中的 HelloService 中将会返回两个构造函数作为候选结果

Spring 海选二

第一场海选的场地为 : ConstructorResolver#autowireConstructor

// 冠军构造函数
Constructor<?> constructorToUse = null;
// 冠军构造函数的参数
ArgumentsHolder argsHolderToUse = null;
// 先按方法修饰符进行排序、public 优先级最高。然后按照参数个数排序、入参个数越多、优先级越高
AutowireUtils.sortConstructors(candidates);
// 最小的类型差异权重
int minTypeDiffWeight = Integer.MAX_VALUE;
// 模凌两可的构造函数列表
Set<Constructor<?>> ambiguousConstructors = null;
// 筛选过程中出现的异常
LinkedList<UnsatisfiedDependencyException> causes = null;

// candidates 海选一选出的构造函数
for (Constructor<?> candidate : candidates) {
  // 入参个数
   int parameterCount = candidate.getParameterCount();
  // 冠军构造函数已经产生、并且冠军构造函数入参的个数大于当前构造函数的入参个数了、那么就没必要继续往下了
  // 因为 Spring 总想给你它能给的最多的爱
   if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
      break;
   }
  // minNrOfArgs 这里正常都是 0 、可以忽略
   if (parameterCount < minNrOfArgs) {
      continue;
   }
 
  // 尝试从 Spring 中找出构造函数需要的参数、如果找不出、不中断、而是继续下一个构造函数
   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<>();
         }
        // 保存异常、在 Spring 中找不到依赖的构造函数参数
         causes.add(ex);
         continue;
      }
   }
   else {
、      if (parameterCount != explicitArgs.length) {
         continue;
      }
      argsHolder = new ArgumentsHolder(explicitArgs);
   }
// 成功从 Spring 中找出构造函数参数、这里去进行比较找出的对象和参数的类型 (类型差异权重)
   int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
         argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
   // 哪个构造函数的类型差异更小、哪个就能成为冠军构造函数
   if (typeDiffWeight < minTypeDiffWeight) {
      constructorToUse = candidate;
      argsHolderToUse = argsHolder;
      argsToUse = argsHolder.arguments;
      minTypeDiffWeight = typeDiffWeight;
      ambiguousConstructors = null;
   }
   else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
   .... 加入到模凌两可的构造函数列表中
      ambiguousConstructors.add(candidate);
   }
}

if (constructorToUse == null) {
  // 流选了、冠军构造函数一个都没有
}
else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
  // 因为构造函数创建 bean 使用的宽松模式即 mbd.isLenientConstructorResolution() 一直为true
  // 所以即使中途出现模凌两可的构造函数列表、也不会抛出异常
   throw new BeanCreationException(xxx);
}
// 使用 constructorToUse, argsToUse 创建 bean
bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));

代码不方便看的话、这里列一下几个重要的变量

// 冠军构造函数
Constructor<?> constructorToUse = null;
// 冠军构造函数的参数
ArgumentsHolder argsHolderToUse = null;
// 先按方法修饰符进行排序、public 优先级最高。然后按照参数个数排序、入参个数越多、优先级越高
AutowireUtils.sortConstructors(candidates);
// 最小的类型差异权重
int minTypeDiffWeight = Integer.MAX_VALUE;
// 模凌两可的构造函数列表
Set<Constructor<?>> ambiguousConstructors = null;

回到我们文章开头的问题、ApplicationContext 和 Environment 毫无疑问都能从 BeanFactory 中找出来、那么它的差异权重就成为选择哪个构造函数的关键了。

因为是构造函数实例化 bean、所以采用的是宽松模式、也就是 mbd.isLenientConstructorResolution() 返回的是 true。如果是配置类(也就是 FactoryMethod ) 就是严格模式、也就是返回 false。

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

arguments 和 rawArguments 转换之后的参数和原始的参数、我们认为它们是一样的在大多数情况下

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);
}

类型差异权重越小、优先级就越高、即能成为冠军构造函数。

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;
}

我们回到文章的问题、

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);
}

ApplicationContext 从 Spring 中找出来的类型为 AnnotationConfigServletWebServerApplicationContext

result = 2 + 2 + 2 + 2 + 1 = 9 、最终返回结果为 9 - 1024 = -1015

AbstractApplicationContext 的 父类 DefaultResourceLoader 不是 ApplicationContext 的子类或实现、所以终止于此

而 Environment 从 Spring 中找出来的类型为 StandardServletEnvironment

result = 2 + 2 + 1 = 5 最终返回结果为 5 - 1024 = -1019

AbstractEnvironment 的父类为 Object 、没有实现 Environment 接口、所以终止于此。

所以你知道选 1⃣️ 还是 2⃣️ 了吗?

[Spring 源码--Bean 实例化](

CoderLi

2021/12/02  阅读:63  主题:前端之巅同款

作者介绍

CoderLi