本文主要是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属性,那么操作步骤如下
- 解析Constructor-arg的子元素
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
- 将type,name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前的BeanDefinition的ConstructorArgumentValues
的indexedArgumentValues属性中
如果没有指定index属性,那么操作步骤如下:
- 解析constructor-arg的子元素
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
- 将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 又是value if ((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,这个对象封装了ref if (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; } }
从代码上来看,对函数的属性元素的解析,经历了以下的几个过程
- 略过description或者meta
- 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg 上不存在以下的情况
同时既有ref又有value属性
存在ref属性或者value属性且又有子元素 - ref属性的处理,使用RunTimeBeanReference封装对应的ref名称,如:
<constructor-arg ref=“a”>
</constructor-arg> - value属性的处理,使用TypeStringValue封装,如:
<constructor-arg value=“a”> - 子元素的处理
<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 方法中我人还是可以清晰的看到实例化的逻辑
-
如果在 如果 RootBeanDefinition 中存在 facotryMethodName 属性,或者说在配置文件中配置了 factory-method,那么Spring会尝试使用 instantiateusingFactoryMethod(beanName,mbd,args) 方法根据 RootBeanDefinition 中的配置生成 bean 的实例
-
解析构造函数并在构造函数的实例化,因为一个 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) { // 从缓存中取constructor argsToUse = 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() { @Override public 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 中的逻辑清晰明了,我们总览一下整个函数,其实现在功能考虑以下的几个方面
-
构造函数参数的确定
根据 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 方法,并返回能解析到的参数的个数 -
构造函数的确定
经过第一步后已经确定构造函数的参数,接下来的任务就是根据构造函数参数锁定的对应的构造函数,而匹配的方法就是根据参数的匹配,所以在这个匹配之前需要先对构造函数按照 public 构造函数优先参数的数量降序,非 public 构造函数参数数量降序,这样可以遍历的情况下迅速判断排在后面的构造函数参数的个数是否符合条件由于配置文件中并不是唯一限制使用参数的位置索引的方式去创建,同样还支持指定参数名称进行设定参数的情况,如<constructor-arg name=“aa”>,那么这种情况下就需要首先确定的构造函数的参数名称,获取参数的名称可以有两种方式,一种是通过注解的方式来获取,另一种就是使用 Spring 中提供的工具类 ParameterNameDiscoverer 来获取,构造函数,参数名称,参数类型,参数值都确定后就可以锁定构造函数以及转换对应的参数类型了 -
根据确定的构造函数转换对应的参数类型
主要是使用 Spring中提供的类型转换器或者用户提供的自定义类型转换器进行转换 -
构造函数不确定性的验证
当然,有时候即使构造函数,参数名称,参数类型,参数值确定后也不一定直接锁定构造函数,不同的构造函数的参数为父子关系,所以 Spring 在最后双做了一次验证 -
根据实例化策略以及得到的构造函数及构造函数参数实例化 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