欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

21--Spring创建Bean的过程(三),构造函数实例化源码解析

程序员文章站 2022-05-23 15:53:30
...

在上一篇我们已经分析了无参构造函数(默认构造函数)实例化,总体来讲还是比较简单的,本篇我们来分析通过有参构造函数实例化,本篇的源码稍微有点长,也比较复杂,其中的一些知识点,可能会有分析的不明确的地方,欢迎大家指正。

在分析之前,我们回顾一下通过构造函数实例化bean的方式,在10–Spring 实例化bean的三种方式我们已经有所介绍,但是为了更好的方便大家分析源码,我们会新建bean和配置文件,在bean中写多个构造函数,然后通过分析源码,了解Spring是如何去确定到底使用哪一个构造函数的。

1.新建demo
  • 新建bean
package com.lyc.cn.day10;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public interface Animal {
    void sayHello();
}
package com.lyc.cn.day10;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public class Cat implements Animal {
    /** 名称 **/
    private String name;

    /** 年龄 **/
    private int age;

    @Override
    public void sayHello() {
        System.out.println("\n大家好,我是一只名叫" + getName() + "的猫");
    }

    /** 构造函数1 **/
    public Cat() {
    }

    /** 构造函数2 **/
    public Cat(String name) {
        this.name = name;
    }

    /** 构造函数3 **/
    public Cat(int age) {
        this.age = age;
    }

    /** 构造函数4 **/
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

新建了一个Animal接口和Cat实现类,并在Cat类中配置了四个构造函数

  • 新建day10.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="cat" class="com.lyc.cn.day10.Cat">
        <constructor-arg name="name" type="java.lang.String" value="美美"/>
        <constructor-arg name="age" type="int" value="3"/>
    </bean>

</beans>
  • 新建测试用例
package com.lyc.cn.day10;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:43
 */
public class MyTest {
    private XmlBeanFactory xmlBeanFactory;

    @Before
    public void initXmlBeanFactory() {
        System.out.println("========测试方法开始=======\n");
        xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day10.xml"));
    }

    @After
    public void after() {
        System.out.println("\n========测试方法结束=======");
    }

    @Test
    public void test() {
        Cat cat = xmlBeanFactory.getBean("cat", Cat.class);
        cat.sayHello();
    }
}
  • 运行测试用例
========测试方法开始=======


大家好,我是一只名叫美美的猫

========测试方法结束=======

一个很简单的demo,但是在Cat类里有四个构造函数,那么Spring是如何通过解析配置文件,来确定使用Cat类中哪一个构造函数呢?

2.Spring创建bean,解析构造函数

在分析这个问题之前,我们先大致介绍一下Spring对构造函数的解析过程,以方便大家分析源码

  • 1 判断有无显式指定参数,如果有则优先使用,如xmlBeanFactory.getBean(“cat”, “美美”,3);
  • 2 没有显式指定参数,则解析配置文件中的参数
  • 3 优先尝试从缓存中获取,spring对参数的解析过程是比较复杂也耗时的,所以这里先尝试从缓存中获取已经解析过的构造函数参数
  • 4 缓存中不存在,则需要解析构造函数参数,以确定使用哪一个构造函数来进行实例化
  • 5 使用指定的构造函数(如果有的话)。
  • 6 如果指定的构造函数不存在,则根据方法访问级别,获取该bean所有的构造函数
  • 7 对构造函数按照参数个数和参数类型进行排序,参数最多的构造函数会排在第一位
  • 8 循环所有bean类中的构造函数,解析确定使用哪一个构造函数
  • 9 获取ArgumentsHolder对象,直接理解为一个参数持有者即可
  • 10 通过构造函数参数权重对比,得出最适合使用的构造函数
  • 11 异常处理
  • 12 缓存解析的构造函数
  • 13 获取实例化策略并执行实例化
  • 14 返回BeanWrapper包装类

打开AbstractAutowireCapableBeanFactory类的createBeanInstance,该方法我们在前面的章节已经分析过,

// ④ 确定需要使用的构造函数(该处涉及一些Aop的知识,我们在讲解Aop时再说明)
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
        mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
    return autowireConstructor(beanName, mbd, ctors, args);
}

再打开autowireConstructor()->autowireConstructor(),该方法就是对构造函数的解析了,代码很长。。。。

public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
            @Nullable Constructor<?>[] chosenCtors, @Nullable final Object[] explicitArgs) {

    BeanWrapperImpl bw = new BeanWrapperImpl();
    this.beanFactory.initBeanWrapper(bw);

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

    // ① 判断有无显式指定参数,如果有则优先使用,如xmlBeanFactory.getBean("cat", "美美",3);
    if (explicitArgs != null) {
        argsToUse = explicitArgs;
    }
    // ② 没有显式指定参数,则解析配置文件中的参数
    else {
        Object[] argsToResolve = null;
        // ③ 优先尝试从缓存中获取,spring对参数的解析过程是比较复杂也耗时的,所以这里先尝试从缓存中获取已经解析过的构造函数参数
        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, true);
        }
    }

    // ④ 缓存中不存在,则需要解析构造函数参数,以确定使用哪一个构造函数来进行实例化
    if (constructorToUse == null) {
        boolean autowiring = (chosenCtors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
        ConstructorArgumentValues resolvedValues = null;

        // 这里定义了一个变量,来记录最小的构造函数参数个数,其作用可以参见下面解释...
        int minNrOfArgs;
        if (explicitArgs != null) {
            minNrOfArgs = explicitArgs.length;
        }
        else {
            ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
            resolvedValues = new ConstructorArgumentValues();
            minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
        }

        // Take specified constructors, if any.
        // ⑤ 使用指定的构造函数(如果有的话)。
        // 注意: ⑤① 这里说的指定构造函数,并不是我们在配置文件中指定的构造函数,而是通过解析SmartInstantiationAwareBeanPostProcessor得出的构造函数
        //           参见AbstractAutowireCapableBeanFactory-->determineConstructorsFromBeanPostProcessors(beanClass, beanName),
        //           就是在本方法被调用之前执行的解析操作
        //      ⑤② 即便解析出来的构造函数不为空,但是大家要注意,candidates是个数组,下一步依然还是要对candidates进行解析,以确定使用哪一个构造函数进行实例化
        Constructor<?>[] candidates = chosenCtors;
        if (candidates == null) {
            Class<?> beanClass = mbd.getBeanClass();
            try {
                // ⑥ 如果指定的构造函数不存在,则根据方法访问级别,获取该bean所有的构造函数
                // 对于本例来分析,应该会获取到四个构造函数Cat(),Cat(String name),Cat(int age),Cat(String name, int age)
                // 注意:该处获取到的构造函数,并不是配置文件中定义的构造函数,而是bean类中的构造函数
                candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors());
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Resolution of declared constructors on bean Class [" + beanClass.getName() +
                        "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
            }
        }

        // ⑦ 对构造函数按照参数个数和参数类型进行排序,参数最多的构造函数会排在第一位
        AutowireUtils.sortConstructors(candidates);
        int minTypeDiffWeight = Integer.MAX_VALUE;
        Set<Constructor<?>> ambiguousConstructors = null;
        LinkedList<UnsatisfiedDependencyException> causes = null;

        // ⑧ 循环所有bean类中的构造函数,解析确定使用哪一个构造函数
        for (Constructor<?> candidate : candidates) {
            Class<?>[] paramTypes = candidate.getParameterTypes();

            // 如果constructorToUse不为空,且参数个数大于当前循环的构造函数参数个数,则直接终止循环,因为解析的bean类构造函数已经经过排序
            // 问题: 进入该if语句的条件是constructorToUse==null? 该处判断是否多余.. ?
            // 带着这个问题,可以接着看源码...该处的设计还是值得参考的
            if (constructorToUse != null && argsToUse.length > paramTypes.length) {
                break;
            }

            // 如果从bean类中解析到的构造函数个数小于从beanDefinition中解析到的构造函数个数,
            // 那么肯定不会使用该方法实例化,循环继续
            // 简单的理解:beanDefinition中的构造函数和bean类中的构造函数参数个数不相等,那么肯定不会使用该构造函数实例化
            if (paramTypes.length < minNrOfArgs) {
                continue;
            }

            // ⑨ 获取ArgumentsHolder对象,直接理解为一个参数持有者即可
            ArgumentsHolder argsHolder;
            if (resolvedValues != null) {
                try {
                    String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length);
                    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) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex);
                    }
                    // Swallow and try next constructor.
                    if (causes == null) {
                        causes = new LinkedList<>();
                    }
                    causes.add(ex);
                    continue;
                }
            }
            else {
                // Explicit arguments given -> arguments length must match exactly.
                if (paramTypes.length != explicitArgs.length) {
                    continue;
                }
                argsHolder = new ArgumentsHolder(explicitArgs);
            }

            // ⑩ 通过构造函数参数权重对比,得出最适合使用的构造函数
            // 先判断是返回是在宽松模式下解析构造函数还是在严格模式下解析构造函数。(默认是宽松模式)
            // 1.对于宽松模式:例如构造函数为(String name,int age),配置文件中定义(value="美美",value="3")
            //   那么对于age来讲,配置文件中的"3",可以被解析为int也可以被解析为String,
            //   这个时候就需要来判断参数的权重,使用ConstructorResolver的静态内部类ArgumentsHolder分别对字符型和数字型的参数做权重判断
            //   权重越小,则说明构造函数越匹配
            // 2.对于严格模式:严格返回权重值,不会根据分别比较而返回比对值
            // 3.minTypeDiffWeight = Integer.MAX_VALUE;而权重比较返回结果都是在Integer.MAX_VALUE做减法,起返回最大值为Integer.MAX_VALUE
            int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                    argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
            // Choose this constructor if it represents the closest match.
            // 如果此构造函数表示最接近的匹配,则选择此构造函数
            if (typeDiffWeight < minTypeDiffWeight) {
                // 将解析到的构造函数赋予constructorToUse,这也是我们上面问题疑问的答案所在处
                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);
            }
        }

        // 11 异常处理
        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 " +
                    "(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 in bean '" + beanName + "' " +
                    "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
                    ambiguousConstructors);
        }

        // 12 缓存解析的构造函数
        if (explicitArgs == null) {
            argsHolderToUse.storeCache(mbd, constructorToUse);
        }
    }

    try {
        // 13 获取实例化策略并执行实例化
        final InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
        Object beanInstance;

        if (System.getSecurityManager() != null) {
            final Constructor<?> ctorToUse = constructorToUse;
            final Object[] argumentsToUse = argsToUse;
            beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
                    strategy.instantiate(mbd, beanName, this.beanFactory, ctorToUse, argumentsToUse),
                    this.beanFactory.getAccessControlContext());
        }
        else {
            beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
        }

        // 14 返回BeanWrapper包装类
        bw.setBeanInstance(beanInstance);
        return bw;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                "Bean instantiation via constructor failed", ex);
    }
}

该函数的步骤,就是我们上面介绍的步骤,该过程还是比较复杂的,注释写的很详细了,就不一一分析了,大家可以运行测试用例,多观察变量值,注意:代码对构造函数参数的权重分析不是很明确,了解的同学可以多加评论。。。