Spring推断构造方法(中)

《推断构造方法(上)》中介绍了通过determineConstructorsFromBeanPostProcessors()获取候选的构造方法,完成构造方法的获取后,下一步就是要推断使用哪一个构造方法,并根据构造方法实例化一个bean

如果候选的构造方法不为空,或者指定了注入方式为构造注入,或者通过BeanDefinition指定了构造方法的参数值,或者通过getBean()获取实例的时候传入了构造方法参数值,对于这些情况,都需要进行构造注入,调用autowireConstructor()完成实例化

如果以上情况都不满足,就会使用无参构造方法进行实例化

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

// 如果推断出来了构造方法,则需要给构造方法赋值,也就是给构造方法参数赋值,也就是构造方法注入
// 如果没有推断出来构造方法,但是autowiremode为AUTOWIRE_CONSTRUCTOR,则也可能需要给构造方法赋值,因为不确定是用无参的还是有参的构造方法
// 如果通过BeanDefinition指定了构造方法参数值,那肯定就是要进行构造方法注入了
// 如果调用getBean的时候传入了构造方法参数值,那肯定就是要进行构造方法注入了
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.
// 不匹配以上情况,则直接使用无参构造方法
return instantiateBean(beanName, mbd);

简单介绍一下上面第二种和第三种情况

  • 指定注入类型

    通过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"/>
    

    通过BeanDefinition指定构造注入

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    //通过BeanDefintion指定为构造注入
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    context.registerBeanDefinition("userService",beanDefinition);
    
  • BeanDefinition指定构造方法参数值

    为构造方法指定参数值

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
    beanDefinition.setBeanClass(UserService.class);
    //指定构造方法参数值
    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new OrderService());
    beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
    context.registerBeanDefinition("userService",beanDefinition);
    

    根据索引为构造方法的指定参数指定参数值

    为构造方法的第一个参数指定参数值

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

一、初步确定构造方法和参数

使用constructorToUse变量存将要使用的构造方法,argsToUse变量存需要使用的构造方法参数值,ArgumentsHolder是对构造方法参数值的封装

首先判断getBean()方法调用时有没有指定构造方法参数,如果指定了,则优先使用指定的值作为构造方法的参数值

如果没有指定,则判断BeanDefinition中是否缓存了构造方法参数值

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

   // 如果getBean()传入了args,那构造方法要用的入参就直接确定好了
   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.1 推断使用默认无参构造方法

先获取前面推断时的到的候选构造方法数组,如果为null,说明有两种情况:1、只有一个默认的无参构造方法 2、有多个有参构造方法,只有这两种情况会返回null

如果没有候选的构造的方法,则直接根据beanClass去获取构造方法

如果只有一个构造方法,并且getBean()调用时没有指定构造方法的参数,BeanDefinition中也没有指定构造方法的参数值,则使用默认的无参构造方法来实例化,同时将构造方法以及参数(空数组)进行缓存,防止后面不必要的构造推断

// 如果没有确定要使用的构造方法,或者确定了构造方法但是所要传入的参数值没有确定
if (constructorToUse == null || argsToUse == null) {
   // Take specified constructors, if any.
   // 如果没有指定构造方法,那就获取beanClass中的所有构造方法所谓候选者
   Constructor<?>[] candidates = chosenCtors;
   if (candidates == null) {
      Class<?> beanClass = mbd.getBeanClass();
      candidates = (mbd.isNonPublicAccessAllowed() ?
               beanClass.getDeclaredConstructors() : beanClass.getConstructors());
   }
   // 如果只有一个候选构造方法,并且没有指定所要使用的构造方法参数值,并且该构造方法是无参的,那就直接用这个无参构造方法进行实例化了
   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 记录构造方法的最小参数个数

首先判断是否需要进行构注入,如果候选的构造方法不为空,或指定了注入方式为构造注入时,都需要进行构造注入

而候选的构造方法不为空包含两种情况:1、有加了@Autowired的构造方法 2、只有一个多参的构造方法,对于这两种情况,都需要进行构造注入

然后记录构造方法所需的最小参数个数,如果getBean()调用的时候,指定了构造方法参数,则最小的参数个数就是getBean()中指定的参数个数;如果通过BeanDefinition的方式指定了构造方法参数,则通过指定的最大索引位置的来计算最小的参数个数,在文章前面讲到了BeanDefinition可以通过指定索引位置的参数值,如果指定的索引值为2,说明要使用的构造方法最少包含3个参数值

记录构造方法的最小参数个数主要就是为了筛选构造方法,如果指定了构造方法的参数个数为2,那么只有一个参数的构造方法就无法使用,只用参数个数大于等于2的构造方法才有可能被使用

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

// 确定要选择的构造方法的参数个数的最小值,后续判断候选构造方法的参数个数如果小于minNrOfArgs,则直接pass掉
int minNrOfArgs;
if (explicitArgs != null) {
    // 如果直接传了构造方法参数值,那么所用的构造方法的参数个数肯定不能少于
    minNrOfArgs = explicitArgs.length;
}
else {
    // 如果通过BeanDefinition传了构造方法参数值,因为有可能是通过下标指定了,比如0位置的值,2位置的值,虽然只指定了2个值,但是构造方法的参数个数至少得是3个
    ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
    resolvedValues = new ConstructorArgumentValues();
    // 处理RuntimeBeanReference
    minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
}
2.3 对构造方法进行排序

在得到构造方法后,按照public类型、参数个数进行排序,也就是优先使用public类型,参数最多的构造方法进行实例化

// 对候选构造方法进行排序,public的方法排在最前面,都是public的情况下参数个数越多越靠前
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 遍历构造方法进行筛选

后面的逻辑获取参数值以及对构造方法计算分数都是在遍历构造方法中完成的

如果已经找到了可用的构造方法,再下次遍历的时候,需要比较上一个构造方法的参数个数是否比当前构造方法的参数个数,如果比当前的构造方法多,则认为上一个可用的构造方法就是最合适的

如果当前构造方法的参数个数比要求的最小参数个数还少,该构造方法直接被过滤掉,不可用

// 遍历每个构造方法,进行筛选
for (Constructor<?> candidate : candidates) {
   // 参数个数
   int parameterCount = candidate.getParameterCount();

   // 本次遍历时,之前已经选出来了所要用的构造方法和入参对象,并且入参对象个数比当前遍历到的这个构造方法的参数个数多,则不用再遍历,退出循环
   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;
   }
   // 如果参数个数小于所要求的参数个数,则遍历下一个,这里考虑的是同时存在public和非public的构造方法
   if (parameterCount < minNrOfArgs) {
      continue;
   }
    ……
}
2.5 寻找构造方法参数

resolvedValues变量记录了通过BeanDefinition指定的构造方法参数值,如果BeanDefinition没有指定,getBean()也没有指定参数值,那么resolvedValues就是一个空的ConstructorArgumentValues对象,但不是null

@ConstructorProperties可以用于构造方法上,指定构造方法的参数名,如果加了该注解,就使用指定的名称作为参数名;如果没有加该注解,就通过反射技术去获取到构造方法定义时的参数名

然后调用createArgumentArray()去获取参数,如果getBean()指定了参数值,判断当前遍历的构造方法的参数个数是否与指定的参数个数相同,如果个数相同,就算找到当前构造方法的参数了,后面再去判断构造方法是否是最佳的

ArgumentsHolder argsHolder;
Class<?>[] paramTypes = candidate.getParameterTypes();
// 没有通过getBean()指定构造方法参数值
if (resolvedValues != null) {
   try {
      // 如果在构造方法上使用了@ConstructorProperties,那么就直接取定义的值作为构造方法的参数名
      String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);

      // 获取构造方法参数名
      if (paramNames == null) {
         ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
         if (pnd != null) {
            paramNames = pnd.getParameterNames(candidate);
         }
      }

      // 根据参数类型、参数名找到对应的bean对象
      argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
            getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
   }
   catch (UnsatisfiedDependencyException ex) {
      // 当前正在遍历的构造方法找不到可用的入参对象,记录一下
      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.
   // 没有通过BeanDefinition指定构造方法参数值,但是在调getBean方法是传入了参数值,那就表示只能用对应参数个数的构造方法
   if (parameterCount != explicitArgs.length) {
      continue;
   }
   // 不用再去BeanFactory中查找bean对象了,已经有了,同时当前正在遍历的构造方法就是可用的构造方法
   argsHolder = new ArgumentsHolder(explicitArgs);
}

createArgumentArray()方法中,会去遍历参数类型,查找参数值

方法入参resolvedValues中存放的是BeanDefinition中指定的参数值,resolvedValues包含了按索引指定的参数值集合和通用的参数集合,如果resolvedValues中指定了参数值,会首先会从indexedArgumentValues(指定索引的参数Map)中去找,再从genericArgumentValues(通用参数集合)去找,如果找到了,再去判断是否需要进行类型转换以及类型转换

如果resolvedValues中没有指定参数值,则通过autowiring入参判断是否需要进行注入,只有需要注入时,Spring才会为构造方法去寻找注入值;如果不需要注入意味着找不到构造方法的参数值,则直接抛异常

如果需要注入,会调用resolveAutowiredArgument(),该方法会调用核心的resolveDependency(),根据类型、名称取查找Bean实例,依赖注入时,为属性查找注入值也是使用resolveDependency(),关于该方法,在《依赖注入(下)》有详细介绍

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

    // 遍历构造方法的参数类型
    for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {

        // 拿到具体的参数类型和参数名
        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;
        // 如果在BeanDefinition中指定了构造方法参数值,则拿到具体的对象
        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);
            //省略的代码主要处理类型转换
            ……

            // 当前所遍历的参数所对应的参数值
            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 {
                // 根据方法参数类型和名字从BeanFactory中匹配Bean对象
                Object autowiredArgument = resolveAutowiredArgument(
                    methodParam, beanName, autowiredBeanNames, converter, fallback);

                // 当前所遍历的参数所对应的参数值
                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 寻找最佳的构造方法

前面已经找到了构造方法的参数值,接着将对每个构造方法进行一个打分,分值最小的构造方法,认为是最匹配的构造方法

其中打分又分为宽松模式和严格模式:

在严格模式下,如果最终参数与参数类型不匹配(经过类型转换),直接返回Integer.MAX_VALUE,表示非常不匹配;如果原始参数与当前类型也不匹配,返回Integer.MAX_VALUE - 512;如果最终参数和原始参数都与参数类型匹配,则返回Integer.MAX_VALUE - 1024

在宽松模式下,如果最终值与参数类型不匹配,直接返回Integer.MAX_VALUE;如果值继承了父类和实现了接口,会根据值的类型和参数类型来计算分数,每增加一层父类,分数就会加2,如果是接口,还会再加1

计算得到的分数越小的构造方法,就是越匹配的构造方法,对于分数相同的情况,则记录一下,放进ambiguousConstructors中缓存

int minTypeDiffWeight = Integer.MAX_VALUE;
// 当前遍历的构造方法所需要的入参对象都找到了,根据参数类型和找到的参数对象计算出来一个匹配值,值越小越匹配
// Lenient表示宽松模式
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this constructor if it represents the closest match.
// 值越小越匹配
if (typeDiffWeight < minTypeDiffWeight) {
    constructorToUse = candidate;
    argsHolderToUse = argsHolder;
    argsToUse = argsHolder.arguments;
    minTypeDiffWeight = typeDiffWeight;
    ambiguousConstructors = null;
}
// 值相等的情况下,记录一下匹配值相同的构造方法
else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
    if (ambiguousConstructors == null) {
        ambiguousConstructors = new LinkedHashSet<>();
        ambiguousConstructors.add(constructorToUse);
    }
    ambiguousConstructors.add(candidate);
}

宽松模式下算分规则:比如参数类型为A,而A继承于类B,类B继承与类C,同时A又实现了接口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 生成实例

如果前面的操作,没有找到可用的构造方法,或者找到了多个匹配度相同的构造方法不知道用哪个,这两种情况都会抛出异常

如果getBean()调用没有传入参数,并且找到了构造方法以及要用的入参对象,则对其进行缓存,然后利用构造方法生成实例并返回

// 如果没有可用的构造方法,就取记录的最后一个异常并抛出
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)");
}
// 如果有可用的构造方法,但是有多个
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);
}

// 如果没有通过getBean方法传入参数,并且找到了构造方法以及要用的入参对象则缓存
if (explicitArgs == null && argsHolderToUse != null) {
   argsHolderToUse.storeCache(mbd, constructorToUse);
}

bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值