本文主要是Spring源码有一定基础的小伙伴而言的,因为这里我只想讲一下,Spring对于构造器的注入参数是如何解析,不同参数个数构造器. 相同参数个数,不同参数类型. Spring是如何选择的。

1.在spring配置文件中配置constructor-arg标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.spring_1_100.test_41_50.test41.User"><constructor-arg value="2"><constructor-arg value="1"></bean>
</beans>
2.构建实体类
@Data
public class User {private String userName;private Integer age;private Integer sex;public User() {}public User(String userName, Integer age) {this.userName = userName;this.age = age;} public User(Integer age, Integer sex) {this.age = age;this.sex = sex;}public User(String userName, Integer age, Integer sex) {this.userName = userName;this.age = age;this.sex = sex;}
}
3.创建测试方法
public static void main(String[] args) {ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_41_50/spring41.xml");System.out.println(JSON.toJSONString(bf.getBean("user")));
}

【输出结果】
{“age”:2,“sex”:1}

4.更换构造函数的顺序
@Data
public class User {private String userName;private Integer age;private Integer sex;public User() {}public User(Integer age, Integer sex) {this.age = age;this.sex = sex;}public User(String userName, Integer age) {this.userName = userName;this.age = age;}public User(String userName, Integer age, Integer sex) {this.userName = userName;this.age = age;this.sex = sex;}}

【输出结果】
{“age”:1,“userName”:“2”}

构造函数的位置不一样,输出结果确不一样,学习这么多年java,很少见到,代码位置不同,却得到不一样的运行结果。
        对于这个示例我们来看看源码是如何解析的

BeanDefinitionParserDelegate.java
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, "constructor-arg")) {parseConstructorArgElement((Element) node, bd);}}
}
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {// 提取index 属性String indexAttr = ele.getAttribute("index");// 提取type属性String typeAttr = ele.getAttribute("type");// 提取name属性String nameAttr = ele.getAttribute("name");if (StringUtils.hasLength(indexAttr)) {try {/*** 6.解析子元素constructor-arg*  对构造函数是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置都不陌生 ,举个简单的例子来说*  ...*  <beans>*      <!--默认的情况下是按照参数的顺序注入的,当指定的index索引后就可以改变了-->*      <bean id="helloBean" class="com.HelloBean">*          <constructor-arg index = "0">*              <value>郝佳*          </constructor-arg>*          <constructor-arg index="1">*              <value>你好*         </constructor-arg>*      </bean>*  </beans>*  上面的配置是Spring构造函数配置中最佳的基础配置,实现功能就是对HelloBean自动寻找对的函数,并在初始化的时候将设置参数传入进去*/int index = Integer.parseInt(indexAttr);if (index < 0) {error("'index' cannot be lower than 0", ele);} else {try {this.parseState.push(new ConstructorArgumentEntry(index));// 解析ele对应的属性元素Object value = parsePropertyValue(ele, bd, null);ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);if (StringUtils.hasLength(typeAttr)) {valueHolder.setType(typeAttr);}if (StringUtils.hasLength(nameAttr)) {valueHolder.setName(nameAttr);}valueHolder.setSource(extractSource(ele));// 不允许重复指定相同的参数if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {error("Ambiguous constructor-arg entries for index " + index, ele);} else {bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);}} finally {this.parseState.pop();}}} catch (NumberFormatException ex) {error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);}} else {// 如果没有index属性则忽略去属性,自动寻找/**<bean id="user" class="com.spring_1_100.test_41_50.test41.User"><constructor-arg value="2"><constructor-arg value="1"></bean>*/try {this.parseState.push(new ConstructorArgumentEntry());Object value = parsePropertyValue(ele, bd, null);ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);if (StringUtils.hasLength(typeAttr)) {valueHolder.setType(typeAttr);}if (StringUtils.hasLength(nameAttr)) {valueHolder.setName(nameAttr);}valueHolder.setSource(extractSource(ele));bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);} finally {this.parseState.pop();}}
}

这个代码看起来复杂,但是涉及的逻辑其实并不复杂,首先是提取constructor-arg上必要的属性(index,type,name)
如果配置中指定的index属性,那么操作步骤如下

  1. 解析Constructor-arg的子元素
  2. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
  3. 将type,name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前的BeanDefinition的ConstructorArgumentValues
    的indexedArgumentValues属性中

如果没有指定index属性,那么操作步骤如下:

  1. 解析constructor-arg的子元素
  2. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
  3. 将type,name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前的BeanDefinition的ConstructorArgumentValues
    的genericArgumentValues属性中

可以看到,对于是否制定的index属性来讲,Spring处理流程是不同的,关键在于属性信息被保存的位置
那么整个流程后,我们尝试着进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:

public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {String elementName = (propertyName != null) ?" element for property '" + propertyName + "'" :" element";// 获取中的所有的子元素,只能是ref,value,list,etc中的一种类型// Should only have one child element: ref, value, list, etc.NodeList nl = ele.getChildNodes();Element subElement = null;for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);// 子元素是description和meta属性 不做处理if (node instanceof Element && !nodeNameEquals(node, "description") &&!nodeNameEquals(node, "meta")) {// Child element is what we're looking for.if (subElement != null) {error(elementName + " must not contain more than one sub-element", ele);} else {// 当property元素包含子元素subElement = (Element) node;}}}// 解析constructor-arg 的ref 属性boolean hasRefAttribute = ele.hasAttribute("ref");// 解析constructor-arg 上的value属性boolean hasValueAttribute = ele.hasAttribute("value");// 判断属性值是ref还是value,不允许既是ref 又是valueif ((hasRefAttribute && hasValueAttribute) ||((hasRefAttribute || hasValueAttribute) && subElement != null)) {/*** 在constructor-arg上不存在:* 1.同时既有ref属性又有value属性* 2.存在ref属性或者value属性且又有子元素*/error(elementName +" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);}// 如果属性值是ref ,创建一个ref 的数据对象,RuntimeBeanReference,这个对象封装了refif (hasRefAttribute) {String refName = ele.getAttribute("ref");if (!StringUtils.hasText(refName)) {error(elementName + " contains empty 'ref' attribute", ele);}//一个指向运行是所依赖对象的引用  ref属性的处理,使用RuntimeBeanReference封装对应的ref名称RuntimeBeanReference ref = new RuntimeBeanReference(refName);ref.setSource(extractSource(ele));return ref;// 如果属性值是value,创建一个value数据对象,typedStringValue,这个对象封装了value} else if (hasValueAttribute) {// 一个持有String类型的对象TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));// 设置这个value的数据对象被当前对象所引用valueHolder.setSource(extractSource(ele));return valueHolder;} else if (subElement != null) {// 解析子元素return parsePropertySubElement(subElement, bd);} else {// 属性值既不是ref也不是value,解析出错,返回null// Neither child element nor "ref" or "value" attribute found.error(elementName + " must specify a ref or value", ele);return null;}
}

从代码上来看,对函数的属性元素的解析,经历了以下的几个过程

  1. 略过description或者meta
  2. 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg 上不存在以下的情况
    同时既有ref又有value属性
    存在ref属性或者value属性且又有子元素
  3. ref属性的处理,使用RunTimeBeanReference封装对应的ref名称,如:
    <constructor-arg ref=“a”>
    </constructor-arg>
  4. value属性的处理,使用TypeStringValue封装,如:
    <constructor-arg value=“a”>
  5. 子元素的处理
  <constructor-arg ><map><entry key="key" value="value"></map></constructor-arg>

而对于子元素的处理,例如,这里反映到的在构造函数中嵌入了子元素map是怎样实现的呢?parsePropertySubElement中对实现了对各种子元素的处理

封装到ConstructorArgumentValues的内部类ValueHolder的value属性中,并加入到了ConstructorArgumentValues的genericArgumentValues属性中。
那我们先来看看addGenericArgumentValue方法的具体实现

addGenericArgumentValue()

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { // Make sure bean class is actually resolved at this point.// 确认Bean是可实例化的Class<?> beanClass = resolveBeanClass(mbd, beanName); // 使用工厂方法对Bean进行实例化,//  getModifiers  得到的就是 前面的 的修饰符 ,这个方法 字段和方法 都有。这个方法的值是 修饰符 相加的到的值。/*** public class Test1 {**     String c;*     public String a;*     private String b;*     protected String d;*     static String e;*     final String f="f";** }**  Field[] fields = Test1.class.getDeclaredFields();*         for( Field field: fields) {*             System.out.println( field.getName() +":" + field.getModifiers() );*         }* c:0* a:1* b:2* d:4* e:8* f:16*/// 所以:什么都不加 是0 , public  是1 ,private 是 2 ,protected 是 4,static 是 8 ,final 是 16。if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());} // 如果工厂方法不不为空,则使用工厂方法初始化策略if (mbd.getFactoryMethodName() != null)  { // 调用工厂方法进行实例化return instantiateUsingFactoryMethod(beanName, mbd, args);}// 使用容器的自动装配方法进行实例化boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {// 一个类有多个构造函数都有不同的参数,所以调用需要根据参数锁定构造函数或者对应的工厂方法 | 一个类有多个构造函数,每个构造函数都有不同的参数,// 所以调用前需要先根据参数锁定构造函数或者工厂方法if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}} //  如果已经解析过,则使用解析好的构造函数方法不需要再次锁定if (resolved) {if (autowireNecessary) { // 配置了自动装配属性,使用了容器的自动装配进行实例化//容器的自动装配根据参数的类型匹配Bean的构造方法构 ,造函数自动注入return autowireConstructor(beanName, mbd, null, null);}else { // 使用了默认无参构造方法进行实例化 return instantiateBean(beanName, mbd);}} // Need to determine the constructor...// 使用了Bean的构造方法进行实例化 |  需要根据参数解析构造函数Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null ||mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  { // 使用了容器的自动装配特性,调用匹配的构造方法进行实例化 , 构造函数自动注入 ,带参数实例化,带有参数实例化的过程相当的复杂,因为存在不确定性。return autowireConstructor(beanName, mbd, ctors, args);} // 使用了默认的无参的构造方法进行实例化return instantiateBean(beanName, mbd);
}

在CreateBeanInstance()方法中,根据指定的初始化策略,使用了简单的工厂,工厂方法或者容器的自动装配生成java实例对象,创建对象的代码如下
创建Bean的实例对象
虽然代码中实例化的细节非常的复杂,但是存在 createBeanInstance 方法中我人还是可以清晰的看到实例化的逻辑

  1. 如果在 如果 RootBeanDefinition 中存在 facotryMethodName 属性,或者说在配置文件中配置了 factory-method,那么Spring会尝试使用 instantiateusingFactoryMethod(beanName,mbd,args) 方法根据 RootBeanDefinition 中的配置生成 bean 的实例

  2. 解析构造函数并在构造函数的实例化,因为一个 bean 对应的类中可能会有多个构造函数,而每个构造函数的参数不同,Spring 在根据参数及类型去判断最终会使用哪个构造函数进行实例化,但是判断的过程是个比较消耗性能的步骤,所以采用缓存机制 ,如果已经解析过,则不需要重复解析而是直接从rootBeanDefinition 中的属性 resolvedConstructorOrFactoryMethod 缓存的值去取,否则需要再次解析,并将解析的结果添加至 RootBeanDefinition中的属性 resolvedConstructorOrFactoryMethod 中

在上述代码解析过程中,mbd.hasConstructorArgumentValues()的值不为空,因此会使用带参数的构造器注入。

protected BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor<?>[] ctors, Object[] explicitArgs) {return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
ConstructorResolver.java
public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd, Constructor<?>[] chosenCtors, final Object[] explicitArgs) {BeanWrapperImpl bw = new BeanWrapperImpl();this.beanFactory.initBeanWrapper(bw);Constructor<?> constructorToUse = null;ConstructorResolver.ArgumentsHolder argsHolderToUse = null;Object[] argsToUse = null;// explicitArgs 通过 getBean 方法传入// 如果 getBean 方法调用的时候指定方法参数那么直接使用if (explicitArgs != null) {argsToUse = explicitArgs;}else {//如果在 getBean 方法时候没有指定则尝试从配置文件中解析Object[] argsToResolve = null;// 尝试从缓存中获取synchronized (mbd.constructorArgumentLock) {constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;if (constructorToUse != null && mbd.constructorArgumentsResolved) {// 从缓存中取constructorargsToUse = mbd.resolvedConstructorArguments;if (argsToUse == null) {// 配置的构造函数参数argsToResolve = mbd.preparedConstructorArguments;}}}// 如果缓存中存在if (argsToResolve != null) {// 解析参数类型,如果给定方法的构造函数 A(int ,int ) 则通过此方法后就会把配置中的("1","1") 转换成(1,1)argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve);}}if (constructorToUse == null) {// Need to resolve the constructor.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.Constructor<?>[] candidates = chosenCtors;if (candidates == null) {Class<?> beanClass = mbd.getBeanClass();try {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);}} // 排序给定的构造函数,首先public 排前面,非 public 排后,其次参数个数降序AutowireUtils.sortConstructors(candidates);int minTypeDiffWeight = Integer.MAX_VALUE;Set<Constructor<?>> ambiguousConstructors = null;LinkedList<UnsatisfiedDependencyException> causes = null;for (int i = 0; i < candidates.length; i++) {Constructor<?> candidate = candidates[i];Class<?>[] paramTypes = candidate.getParameterTypes();if (constructorToUse != null && argsToUse.length > paramTypes.length) {// Already found greedy constructor that can be satisfied ->// do not look any further, there are only less greedy constructors left.// 如果已经找到选用的构造函数或者需要的参数个数小于当前的构造函数个数的终止,因此 已经按照参数个数降序排序break;}// 参数个数小于配置了constructor-arg配置个数,直接排除if (paramTypes.length < minNrOfArgs) {continue;}ConstructorResolver.ArgumentsHolder argsHolder;if (resolvedValues != null) {try {// 有参数则根据值构造对应的参数类型的参数// 注释上获取参数的名称String[] paramNames = ConstructorResolver.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, candidate, autowiring);}catch (UnsatisfiedDependencyException ex) {if (this.beanFactory.logger.isTraceEnabled()) {this.beanFactory.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 ConstructorResolver.ArgumentsHolder(explicitArgs);}// 探测是否有不确定性构造函数存在,例如不同的构造函数的参数为父子关系int typeDiffWeight = (mbd.isLenientConstructorResolution() ?argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));// Choose this constructor if it represents the closest match.// 如果它代表着当前最接近匹配则选择作为构造函数// 我们在这个函数中分析过createArgumentArray ,即使找到了合适的她,Spring 还是要// 比对一下的,就像人找对象一样,要看对方家里条件,条件更好的居上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<Constructor<?>>();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 " +"(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);}if (explicitArgs == null) {// 将解析的构造函数加入到缓存中argsHolderToUse.storeCache(mbd, constructorToUse);}}try {Object beanInstance;if (System.getSecurityManager() != null) {final Constructor<?> ctorToUse = constructorToUse;final Object[] argumentsToUse = argsToUse;beanInstance = AccessController.doPrivileged(new PrivilegedAction() {@Overridepublic Object run() {return beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, beanFactory, ctorToUse, argumentsToUse);}}, beanFactory.getAccessControlContext());}else {beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);}// 将构建的实例加入到 BeanWapper 中bw.setWrappedInstance(beanInstance);return bw;}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean instantiation via constructor failed", ex);}
}

对于实例的创建 Spring 中分成两种情况,一种是通用的实例化,另一种是带有参数的实例化,带有的实例化过程相当的复杂,因为存在着不确定性,所以在判断对应的参数做了大量的工作逻辑很复杂,函数代码量很大,不知道你是否坚持读完了整个函数并理解了整个功能呢?笔者觉得这个函数的写法完全不Spring 的一的风格,如果你一直跟笔者分析思路到这里,相信你或多或少对 Spring 的编码风格有所了解,Spring 的一的做法就是将复杂的逻辑分解,分成 N 个小函数的嵌套,每一层都是对下一层的逻辑的总结要概要,这样使得每一层的逻辑会变得简单容易理解,在上面的函数中,包含了很多的逻辑实现,笔者觉得至少应该将逻辑封装在不同的函数而使得在 autowireConstructor 中的逻辑清晰明了,我们总览一下整个函数,其实现在功能考虑以下的几个方面

  1. 构造函数参数的确定
    根据 explicitArgs 参数判断如果传入的参数 explicitArgs 不为空,那边可以直接确定参数,因为 explicitArgs 参数在调用 Bean 的时候用户指定的,在 BeanFactory类中存在这样的方法Object getBean(String name,Object …args ) throws BeansException;在获取 bean 的时候,用户不但可以指定 bean 的名称还可以指定的 bean 所对应的类的构造函数或者工厂方法的参数,主要用于静态工厂方法的调用,而这里需要给定完全匹配的参数,所以,便可以判断,如果传入的参数explicitArgs 不为空,而可以确定构造函数参数就是它了。 缓存中获取, 除此之外 ,确定参数的办法如果之前已经分析过了,也就是说构造函数的参数已经记录在缓存中,那么便可以直接拿来使用,而且,这里要提到是,在缓存中可能是参数的最终类型也可能是参数的初始化类型,例如,构造函数参数要求的是 int 类型,但是原始参数可能是 String 类型,那么即使在缓存中得到了参数,也需要经过类型转换器的过滤以确保参数类 与对应的构造函数完全对应,配置文件的获取,如果不能根据传入的参数 explicitArgs 确定构造函数参数也无法在缓存中得到相关的信息,那么只能开始新一轮的分析了分析获取配置文件中配置的构造函数信息开始,经过之前的分析,我们知道,Spring 中配置文件中的信息经过转换都会通过 BeanDefinition实例承载,也就是参数的 mbd.getConstructorArgmentValues()中来获取配置的构造函数信息,有了配置中的信息就可以获取对应的参数信息,获取参数的信息包括直接指定值,如:直接指定构造函数中的某个值为原始类型的 String 类型,或者是其对其他的bean 的引用,而这一处理委托给 resolveContructorArguments 方法,并返回能解析到的参数的个数

  2. 构造函数的确定
    经过第一步后已经确定构造函数的参数,接下来的任务就是根据构造函数参数锁定的对应的构造函数,而匹配的方法就是根据参数的匹配,所以在这个匹配之前需要先对构造函数按照 public 构造函数优先参数的数量降序,非 public 构造函数参数数量降序,这样可以遍历的情况下迅速判断排在后面的构造函数参数的个数是否符合条件由于配置文件中并不是唯一限制使用参数的位置索引的方式去创建,同样还支持指定参数名称进行设定参数的情况,如<constructor-arg name=“aa”>,那么这种情况下就需要首先确定的构造函数的参数名称,获取参数的名称可以有两种方式,一种是通过注解的方式来获取,另一种就是使用 Spring 中提供的工具类 ParameterNameDiscoverer 来获取,构造函数,参数名称,参数类型,参数值都确定后就可以锁定构造函数以及转换对应的参数类型了

  3. 根据确定的构造函数转换对应的参数类型
    主要是使用 Spring中提供的类型转换器或者用户提供的自定义类型转换器进行转换

  4. 构造函数不确定性的验证
    当然,有时候即使构造函数,参数名称,参数类型,参数值确定后也不一定直接锁定构造函数,不同的构造函数的参数为父子关系,所以 Spring 在最后双做了一次验证

  5. 根据实例化策略以及得到的构造函数及构造函数参数实例化 Bean ,后面的将进行继承的讲解

从断点来看ConstructorArgumentValues有两个属性,一个是Map类型的indexedArgumentValues,用于存储constructor-arg标签中配置了index的ValueHolder,key是index,value是ValueHolder对象,另一个是List类型的genericArgumentValues,用于存储没有配置index的ValueHolder对象,如上图,genericArgumentValues第一个值是2,第二个值是1,和配置文件中的配置相对应

        对于代码中的这一行
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
毫无凝问,上面的minNrOfArgs返回参数是2 ,但是这一句代码中却做了太多的事情,

private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?this.beanFactory.getCustomTypeConverter() : bw);BeanDefinitionValueResolver valueResolver =new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);int minNrOfArgs = cargs.getArgumentCount();//如果constructor-arg中配置了index标签,则进行下面的遍历for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) {int index = entry.getKey();if (index < 0) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Invalid constructor argument index: " + index);}if (index > minNrOfArgs) {minNrOfArgs = index + 1;}ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue();if (valueHolder.isConverted()) {resolvedValues.addIndexedArgumentValue(index, valueHolder);}else {Object resolvedValue =valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());ConstructorArgumentValues.ValueHolder resolvedValueHolder =new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());resolvedValueHolder.setSource(valueHolder);resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);}}//如果constructor-arg中只配置了value ,让Spring自动帮我们匹配,那么会进行genericArgumentValues遍历for (ConstructorArgumentValues.ValueHolder valueHolder : cargs.getGenericArgumentValues()) {if (valueHolder.isConverted()) {resolvedValues.addGenericArgumentValue(valueHolder);}else {//对value值进行重新解析,比如el表达式解析成具体的值,什么环境变量替换,等等,因些resolveValueIfNecessary//这个函数非常的复杂,有兴趣的朋友可以自行研究一下,笔者将来有时间,再关于Spring中对于配置项替换成我们想要的,再写这一篇相关博客了。Object resolvedValue =valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());ConstructorArgumentValues.ValueHolder resolvedValueHolder =new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());resolvedValueHolder.setSource(valueHolder);resolvedValues.addGenericArgumentValue(resolvedValueHolder);}}return minNrOfArgs;
}

我们断点继续往下

        从断点的值中我们可以看到candidates中有4个构造函数,先进行排序,排序给定的构造函数,首先public排前面,非public排后,其次参数个数降序。

我们继续代码跟进

        我们可以看到得到了第一个构造器方法参数名称,那么这些参数名是如何得到的呢?深入其中的源码 ,发现,Spring 先通过反射来获取参数名称,好像一般没有什么用,如果获取不到,再通过ASM来获取函数参数名称,如果这一块的代码理解了,我们就不难想像,为什么Spring MVC 为什么我们前端请求过来的是一个JSON字符串,而到了我们Controller层,具体方法参数的值就被封装进去,这里也不做深入研究,将来有机会再来写相关博客了吧。

对于下面这一行代码,在构造器选择上起到了至关重要的作用。

argsHolder = createArgumentArray(
beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring);
}

createArgumentArray()

private ConstructorResolver.ArgumentsHolder createArgumentArray(String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,boolean autowiring) throws UnsatisfiedDependencyException {String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?this.beanFactory.getCustomTypeConverter() : bw);ConstructorResolver.ArgumentsHolder args = new ConstructorResolver.ArgumentsHolder(paramTypes.length);//每一个ValueHolder被匹配后,都会保存到这里Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =new HashSet<ConstructorArgumentValues.ValueHolder>(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] : null);ConstructorArgumentValues.ValueHolder valueHolder =resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);//autowiring参数,系统默认是 false ,也就是说,在构造函数对应的参数没有找到 ValueHolder时,//不允许默认值替代 ValueHolder ,如果允许的话,就出现这种情况, "2"匹配 userName ,//"1" 匹配 age ,sex 没有找到,那就默认为 null 吧,就直接匹配到了3参数的构造函数,//Spring 默认情况下是不允许这种情况发生的。// getGenericArgumentValue 方法中,是抱着侥幸的心理去找找,看有没有没有被使用的 ValueHolder ,//如果找到了,就可以偿试匹配一下,说不定能成功呢?但是现在是残酷的,//你想获得一生一世的爱情,你就不能脚踏两只船。// 因此在第一个构造函数中,没有找到 ValueHolder ,抛出异常。当没有找到,那么继续找,在第二次// 轮询的过程中,竟然找到了确认过眼神的那个人,那就一生与你相守吧,// 我们以为这样,但是 Spring 才不是一个痴情的人,而是一个势力眼的人,他找到合适的了,他还会继续找// 看有没有更加合适的,当找到更加合适的,可能就会丢弃现有的你。// 在这个函数的外层就可以看到 if (valueHolder == null && !autowiring) {valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);}if (valueHolder != null) {usedValueHolders.add(valueHolder);Object originalValue = valueHolder.getValue();Object convertedValue;if (valueHolder.isConverted()) {convertedValue = valueHolder.getConvertedValue();args.preparedArguments[paramIndex] = convertedValue;}else {ConstructorArgumentValues.ValueHolder sourceHolder =(ConstructorArgumentValues.ValueHolder) valueHolder.getSource();Object sourceValue = sourceHolder.getValue();try {//在这一行的转换中,只要类型转换异常,则抛出异常,说明此构造函数不符合要求convertedValue = converter.convertIfNecessary(originalValue, paramType,MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex));args.resolveNecessary = true;args.preparedArguments[paramIndex] = sourceValue;}catch (TypeMismatchException ex) {throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, paramIndex, paramType,"Could not convert " + methodType + " argument value of type [" +ObjectUtils.nullSafeClassName(valueHolder.getValue()) +"] to required type [" + paramType.getName() + "]: " + ex.getMessage());}}args.arguments[paramIndex] = convertedValue;args.rawArguments[paramIndex] = originalValue;}else {if (!autowiring) {throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, paramIndex, paramType,"Ambiguous " + methodType + " argument types - " +"did you specify the correct bean references as " + methodType + " arguments?");}try {MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter);args.rawArguments[paramIndex] = autowiredArgument;args.arguments[paramIndex] = autowiredArgument;args.preparedArguments[paramIndex] = new ConstructorResolver.AutowiredArgumentMarker();args.resolveNecessary = true;}catch (BeansException ex) {throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);}}}for (String autowiredBeanName : autowiredBeanNames) {this.beanFactory.registerDependentBean(autowiredBeanName, beanName);if (this.beanFactory.logger.isDebugEnabled()) {this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +"' via " + methodType + " to bean named '" + autowiredBeanName + "'");}}return args;
}

因此,我们在Spring 选择对象的时候是比较保险的,同时又是聪明的,先选择看对眼的,然后继续寻找,看有没有看对眼的,家里条件更加好的,如果有的话,那就将现在的对象替换掉,如果没有的话,那就算了,至少先来后到,感情基础还在的。
        本章开头提到的问题,其实是我故意误导大家的,这个和 Spring 解析方式无关,而是与 java 获取反射有关,下面我们来看一个例子。

User.java
@Data
public class User {private String userName;private Integer age;private Integer sex;public User(Integer age, Integer sex) {this.age = age;this.sex = sex;}public User(String userName, Integer age) {this.userName = userName;this.age = age;}public User() {}public User(String userName, Integer age, Integer sex) {this.userName = userName;this.age = age;this.sex = sex;}
}
###### 测试
@Test
public void testxx(){User user = new User();Class clazz = user.getClass();Constructor<?> [] constructors = clazz.getDeclaredConstructors();for(Constructor constructor:constructors){System.out.println(constructor);}
}

【结果输出】
public com.spring_1_100.test_41_50.test41.User(java.lang.String,java.lang.Integer,java.lang.Integer)
public com.spring_1_100.test_41_50.test41.User()
public com.spring_1_100.test_41_50.test41.User(java.lang.String,java.lang.Integer)
public com.spring_1_100.test_41_50.test41.User(java.lang.Integer,java.lang.Integer)

也就是java 代码在获取构造函数的时候,是从下往上获取的,因此,Spring 匹配过程中先匹配到下面的构造函数,所以在两个构造函数同时符合 Spring 要求的时候,Spring先选择先遇到的构造函数,因此,从结果输出来看,就跟代码书写位置有关系了。

对于 Spring 选择条件更好的对象问题,我们再来测试一下。

1.新建Person类
@Data
public class Person {private Integer age;private Number sex;public Person() {}public Person(Number sex) {this.sex = sex;}public Person(Integer age) {this.age = age;}
}

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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="person" class="com.spring_1_100.test_41_50.test41.Person"> <constructor-arg value="2"></bean></beans>

测试代码

@Test
public void test3(){ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_41_50/spring41_1.xml");System.out.println(JSON.toJSONString(bf.getBean("person")));
}

【结果输出】
{“age”:2}
有兴趣的同学不妨多测试一下,无论我们怎样更换构造函数的位置,结果都是一样的,为什么呢?
2 按道理也可以赋值给 sex ,为什么就不可以了呢?我们来看看 Spring 源码
我们来看一下这个代码

ArgumentsHolder.java

public int getTypeDifferenceWeight(Class<?>[] paramTypes) {int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;//选择权重小的,找对象时亦是找年龄小的return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);
}

MethodInvoker.java

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

相同的参数,如果是父类,将权重加2 ,生活场景亦是如此,如果是父类,那么年龄肯定大一点喽,但是当条件一样的情况下,选对象肯定选年轻帅气一点的人做对象喽,其实Spring 在 选择构造函数和我们人找对象是一个道理,下面我们来总结一下。

总结:

首先,Spring会将所有的对象罗列一遍,先找到一个看对眼的人,然后继续找,将其他的所有的对象看一遍,看还有没有看对眼的人,如果有,看家庭条件有没有更好的,如果有,则替换掉,如果没有,那看年龄是不是有更年轻的,如果有,则替换掉,如果都没有,就选第一个看对眼的人吧,至少有感情基础还是在的。

本文github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_41_50/test41

Spring源码深度解析(郝佳)-学习-构造器注入相关推荐

  1. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

  3. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

    在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项 ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-Spring整合MyBatis

    了解了MyBatis的单独使用过程之后,我们再来看看它也Spring整合的使用方式,比对之前的示例来找出Spring究竟为我们做了什么操作,哪些操作简化了程序开发. 准备spring71.xml &l ...

  7. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理(七)

    加载时织入(Load-Time Weaving ,LTW) 指的是在虚拟机加载入字节码文件时动态织入Aspect切面,Spring框架的值添加为 AspectJ LTW在动态织入过程中提供了更细粒度的 ...

  8. Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析

      我们经常在Spring项目中或者Spring Boot项目中使用RabbitMQ,一般使用的时候,己经由前人将配置配置好了,我们只需要写一个注解或者调用一个消息发送或者接收消息的监听器即可,但是底 ...

  9. Spring源码深度解析(郝佳)-学习-RMI使用及Spring源码解读

    java远程方法调用.即Java RMI(Java Remote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口,它使客户机上运行的程序可以调用远 ...

最新文章

  1. 腾讯裁撤中层干部,拥抱年轻人
  2. redis学习笔记---redis的哨兵Sentinel
  3. xxl子任务_阿里面试官:聊一下分布式任务调度有那些解决方案?
  4. ESP8266编译脚本之五
  5. 树莓派AI视觉云台——2、树莓派系统镜像的下载和烧写
  6. apache camel_Apache Camel中的断路器模式
  7. 160 - 49 DueList.4
  8. 关于windows cmd的一些便捷应用
  9. ES6新特性_let使用案例---JavaScript_ECMAScript_ES6-ES11新特性工作笔记004
  10. 语义分割之图像经镜像、翻转、裁剪后像素点的位置映射
  11. 【问题解决方案】visudo: /etc/sudoers is busy, try again later
  12. [转载] Python:numpy中array的用处
  13. 泰安出差,使用产品有所感触
  14. 资源工具分享(第1期):后端架构师技术图谱
  15. 老司机教你如何快速入门Linux | 小白必知
  16. ipa在线安装搭建_免电脑 iOS 12 一键越狱+手机端直接自动安装插件教程
  17. 站群服务器找11火星软件
  18. [Python] 练习代码
  19. CDlinux wifi密码破解(pin码枚举)
  20. oss图片无法在网站中显示

热门文章

  1. android高德地图设置默认显示位置
  2. 火柴棒搭成的几何世界
  3. 成功的秘诀在于长久的坚持和耐心
  4. python自动化高效办公第二期,带你项目实战【一】{excel数据处理、批量化生成word模板、pdf和ppt等自动化操作}
  5. 让电视拥有高清影视,先搞清楚HDMI和VGA的误区
  6. 知识点 - 数论函数导论
  7. java仿天猫_Java仿天猫练手项目分享【免费收藏】
  8. 读书笔记:《亿级流量网站架构核心技术 -- 跟开涛学搭建高可用高并发系统》
  9. css3 画动态微笑猫的最佳实践
  10. 软考A计划-电子商务设计师-电商设计师重点