22. Spring源码篇之推断构造方法

简介

很多时候我们的构造器都不止一个,那么spring怎么选择的呢,签名介绍了推断构造方法的扩展点,可以使用@Autowired注解去选择使用哪个构造器,但是即使这样也有可能有多个Autowired且required为false的构造器,那么还是得选择

前面我们介绍过了@Bean的实例化,其实推断构造器的逻辑与其相差不多。

源码分析

源码在 org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor

// chosenCtors 指定使用哪几个构造器,explicitArgs 参数
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

    BeanWrapperImpl bw = new BeanWrapperImpl();
    // 设置一些类型转换器等
    this.beanFactory.initBeanWrapper(bw);

    Constructor<?> constructorToUse = null;
    ArgumentsHolder argsHolderToUse = null;
    Object[] argsToUse = null;

    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;
                if (argsToUse == null) {
                    argsToResolve = mbd.preparedConstructorArguments;
                }
            }
        }
        if (argsToResolve != null) {
            argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);
        }
    }

    if (constructorToUse == null || argsToUse == null) { // 一般都进入

        Constructor<?>[] candidates = chosenCtors;
        if (candidates == null) {
            // 进入这表示没有确定要使用哪个构造器,那么拿到该类的所有构造器放到candidates候选
            Class<?> beanClass = mbd.getBeanClass();
            candidates = (mbd.isNonPublicAccessAllowed() ?
                        beanClass.getDeclaredConstructors() : beanClass.getConstructors());
        }

        if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
            // 构造器只有一个,没有指定构造器参数,也没有预先设置constructorArgumentValues,那么直接实例化instantiate
            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;
            }
        }

        boolean autowiring = (chosenCtors != null ||
                mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
        ConstructorArgumentValues resolvedValues = null;

        // 要选择构造器参数最多的,如果小于这个值那么pass,如果大于更新
        int minNrOfArgs;
        if (explicitArgs != null) {
            // 如果指定传入了参数值,那么minNrOfArgs不能低于传入的长度
            minNrOfArgs = explicitArgs.length;
        }
        else {
            // 这种方式指定构造器的值有点特殊,前面文章也介绍过 
            // 它可以指定参数下标的值,比如指定了0,2那么表示指定了第一个参数和第三个参数的值,虽然指定参数只有 2个,但是minNrOfArgs也至少得是 3
            ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
            resolvedValues = new ConstructorArgumentValues();
            minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
        }

        // 构造方法进行排序
        // public的方法排在最前面
        // 都是public的参数个数越多越靠前
        AutowireUtils.sortConstructors(candidates);
        
        // 评分
        int minTypeDiffWeight = Integer.MAX_VALUE;
        
        // 模棱两可的构造器,意思就是有多个@构造器,并且推断不出用哪个,是要抛出异常的
        Set<Constructor<?>> ambiguousConstructors = null;
        
        Deque<UnsatisfiedDependencyException> causes = null;

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

            if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
                // 如果说已经选出来要用的构造器和入参对象,但是指定的入参比当前构造器参数还多,那么直接break,因为排了序,后面参数肯定更少
                break;
            }
            
            // 如果参数个数小于要求的参数个数,pass
            if (parameterCount < minNrOfArgs) {
                continue;
            }

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

                    // 找出参数名称,反射 & 本地变量表
                    if (paramNames == null) {
                        ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
                        if (pnd != null) {
                            paramNames = pnd.getParameterNames(candidate);
                        }
                    }

                    // 根据BeanDefinition中定义的参数,以及通过name从beanFactory获取到Bean
                    // 最终组成为argsHolder ,这里面的详细过程后面文章讲
                    argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
                            getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
                }
                catch (UnsatisfiedDependencyException ex) {
                    if (causes == null) {
                        causes = new ArrayDeque<>(1);
                    }
                    // 记录异常,后面推断不出方法便抛出异常
                    causes.add(ex);
                    continue;
                }
            }
            else {
                // resolvedValues为null,那么explicitArgs就一定有值
                // 通过getBean传入的,那么参数个数必须一致
                if (parameterCount != explicitArgs.length) {
                    continue;
                }
                
                argsHolder = new ArgumentsHolder(explicitArgs);
            }

            // 根据参数类型和找到的参数对象计算出来一个匹配值,值越小越匹配
            int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                    argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
            // Choose this constructor if it represents the closest match.
            // 值越小越匹配
            if (typeDiffWeight < minTypeDiffWeight) {
                // 如果根据当前的方法参数计算出来的评分更小些,那么应该使用该构造方法来创建Bean
                constructorToUse = candidate;
                argsHolderToUse = argsHolder;
                argsToUse = argsHolder.arguments;
                minTypeDiffWeight = typeDiffWeight;
                ambiguousConstructors = null; // 同时也就不存在模棱两可的方法了
            }
            
            // 如果评分一样的,那么表示推断不出使用哪个方法构造Bean,最终找不出来要抛异常
            else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
                if (ambiguousConstructors == null) {
                    ambiguousConstructors = new LinkedHashSet<>();
                    ambiguousConstructors.add(constructorToUse);
                }
                ambiguousConstructors.add(candidate);
            }
        }
       
        if (constructorToUse == null) {
            // 表示没有找到
            // 看有没有记录到异常,有抛出
            if (causes != null) {
                UnsatisfiedDependencyException ex = causes.removeLast();
                for (Exception cause : causes) {
                    this.beanFactory.onSuppressedException(cause);
                }
                throw ex;
            }
            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()) {
            // 表示推断不出使用哪个方法构造Bean,抛异常
            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 (explicitArgs == null && argsHolderToUse != null) {
            // 找到了,缓存起来
            argsHolderToUse.storeCache(mbd, constructorToUse);
        }
    }

    // 通过反射调用uniqueCandidate返回一个对象,然后设置到BeanWrapper返回
    bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
    return bw;
}

总结

以上便是推断构造方法的关键逻辑,因为构造方法可能有多个,这个时候如果指定了参数,那么直接根据指定的参数匹配方法,如果没有指定参数,那么spring会根据评分算法帮我们找出方法

至于其中的方法评分的算法,不是重点,大概就是说匹配成都越高的分越低,分越低就优先级越高,如果匹配度不高那么是要加分的,比如当前构造参数类型是值的类型的父类,加两分,当前类型是个接口加一分

关于这个算法,后面出文章讲解


欢迎关注,学习不迷路!

你可能感兴趣的:(spring,framework,spring,java,后端)