版本:spring-framework-4.1

一概述

在看AbstractBeanDefinition源码时,注意到lenientConstructorResolution属性有诸多不疑,现在通过示例及源码分析,一步步揭开面纱。


二、本文希望能解释清楚的几个问题

  1. lenientConstructorResolution属性的作用?
  2. lenientConstructorResolution的值ture与false有什么区别?
  3. 源码中是如何实现的,为何使用权重设计?


三、示例

3.1 applicationContext-Lenient.xml

<bean id="zoo" class="com.bean.Zoo" scope="prototype" factory-method="create">
<constructor-arg>
<bean class="com.bean.Cat"/>
</constructor-arg>
</bean>

3.2 Animal.java

public abstract class Animal {
public void run() {
System.out.println("[abstract class Animal] run ! ");
}
}

3.3 Animal.java

public class Cat extends Animal {
@Override
public void run() {
System.out.println("[class Cat] run !");
}
}

3.4 Zoo.java

public class Zoo {

    private final Animal animal;
public Animal getAnimal() {
return animal;
} public Zoo(){
System.out.println("Zoo() executed!");
animal=null;
} public Zoo(Animal animal){
System.out.println("Zoo(Animal animal) executed!");
this.animal=animal;
} public Zoo(Cat cat){
System.out.println("Zoo(Cat cat) executed!");
this.animal=cat;
} public static Zoo create(){
return new Zoo();
} public static Zoo create(Animal animal){
return new Zoo(animal);
} public static Zoo create(Cat cat){
return new Zoo(cat);
}
}

3.5 宽松模式测试:

@Test
public void testLenient() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions("applicationContext-Lenient.xml");
AbstractBeanDefinition bd = (AbstractBeanDefinition) xbf.getBeanDefinition("zoo");
bd.setLenientConstructorResolution(true);
try {
Zoo zoo=(Zoo)xbf.getBean("zoo");
zoo.getAnimal().run();
}
catch (BeanCreationException ex) {
ex.printStackTrace();
}
} 打印结果:
Zoo(Cat cat) executed!
[class Cat] run !

3.6 非宽松模式测试:

@Test
public void testNonLenient() {
DefaultListableBeanFactory xbf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(xbf).loadBeanDefinitions("applicationContext-Lenient.xml");
AbstractBeanDefinition bd = (AbstractBeanDefinition) xbf.getBeanDefinition("zoo");
bd.setLenientConstructorResolution(false);
try {
Zoo zoo=(Zoo)xbf.getBean("zoo");
zoo.getAnimal().run();
}
catch (BeanCreationException ex) {
ex.printStackTrace();
}
} 打印结果:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'zoo' defined in class path resource [applicationContext-Lenient.xml]: Ambiguous factory method matches found in bean 'zoo' (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): [public static com.bean.Zoo com.bean.Zoo.create(com.bean.Animal), public static com.bean.Zoo com.bean.Zoo.create(com.bean.Cat)]

两个测试结果,宽松模式下能够正常构建实例,而在非宽松模式下却抛出BeanCreationException,大致意思就是在zoo中发现了存在歧义的工厂方法。带着问题,分析内部源码。


四、源码分析

在追踪源码时经过了很多的实现,我们只看最关键的几个实现:

4.1 ConstructorResolver.java

public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final RootBeanDefinition mbd, final Object[] explicitArgs) {

    ... ...
if (factoryMethodToUse == null || argsToUse == null) {
//寻找匹配类型的工厂方法,factoryClass=com.bean.Zoo(配置文件中bean定义的)
factoryClass = ClassUtils.getUserClass(factoryClass); //获取factoryClass所有方法,也包括继承来的方法
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd); //过滤出factoryClass中factory-method指定的方法
List<Method> candidateSet = new ArrayList<Method>();
for (Method candidate : rawCandidates) {
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
candidateSet.add(candidate);
}
}
Method[] candidates = candidateSet.toArray(new Method[candidateSet.size()]);
//排序,public构造函数优先参数数量降序、非public构造函数参数数量降序
AutowireUtils.sortFactoryMethods(candidates); ConstructorArgumentValues resolvedValues = null;
//构造器装配,显然我们使用的工厂方法构造,这里autowiring=false
boolean autowiring = (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Method> ambiguousFactoryMethods = null; //引起歧义的方法列表 int minNrOfArgs;
if (explicitArgs != null) {
minNrOfArgs = explicitArgs.length;
}
else {
//提取配置文件中定义的构造函数参数(constructor-arg)
ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
//解析后的参数值
resolvedValues = new ConstructorArgumentValues();
//参数的数量
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
} List<Exception> causes = null; for (int i = 0; i < candidates.length; i++) {
//工厂方法函数,遍历匹配
Method candidate = candidates[i];
Class<?>[] paramTypes = candidate.getParameterTypes(); if (paramTypes.length >= minNrOfArgs) {
ArgumentsHolder argsHolder; if (resolvedValues != null) {
// Resolved constructor arguments: type conversion and/or autowiring necessary.
try {
String[] 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 factory method [" + candidate +
"] of bean '" + beanName + "': " + ex);
}
if (i == candidates.length - 1 && argsHolderToUse == null) {
if (causes != null) {
for (Exception cause : causes) {
this.beanFactory.onSuppressedException(cause);
}
}
throw ex;
}
else {
// Swallow and try next overloaded factory method.
if (causes == null) {
causes = new LinkedList<Exception>();
}
causes.add(ex);
continue;
}
}
} else {
// Explicit arguments given -> arguments length must match exactly.
if (paramTypes.length != explicitArgs.length) {
continue;
}
//无参构造的情况
argsHolder = new ArgumentsHolder(explicitArgs);
}
//重点!!!
//计算权重,宽松模式下执行getTypeDifferenceWeight方法,计算细节请看4.3
//非宽松模式下执行
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this factory method if it represents the closest match.
if (typeDiffWeight < minTypeDiffWeight) {
factoryMethodToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousFactoryMethods = null;
}
//确定歧义函数的逻辑
else if (factoryMethodToUse != null //至少找到一个匹配的方法
&& typeDiffWeight == minTypeDiffWeight //两个参数类型权重相同
&& !mbd.isLenientConstructorResolution() //必须是非宽松模式
//参数类型、长度相等
&& paramTypes.length == factoryMethodToUse.getParameterTypes().length
&& !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
if (ambiguousFactoryMethods == null) {
ambiguousFactoryMethods = new LinkedHashSet<Method>();
ambiguousFactoryMethods.add(factoryMethodToUse);
}
//到这一步,基本确定已找到有歧义的方法
ambiguousFactoryMethods.add(candidate);
}
}
}
//未匹配到可用的工厂方法
if (factoryMethodToUse == null) {
List<String> argTypes = new ArrayList<String>(minNrOfArgs);
if (explicitArgs != null) {
for (Object arg : explicitArgs) {
argTypes.add(arg != null ? arg.getClass().getSimpleName() : "null");
}
}
else {
Set<ValueHolder> valueHolders = new LinkedHashSet<ValueHolder>(resolvedValues.getArgumentCount());
valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());
valueHolders.addAll(resolvedValues.getGenericArgumentValues());
for (ValueHolder value : valueHolders) {
String argType = (value.getType() != null ? ClassUtils.getShortName(value.getType()) :
(value.getValue() != null ? value.getValue().getClass().getSimpleName() : "null"));
argTypes.add(argType);
}
}
String argDesc = StringUtils.collectionToCommaDelimitedString(argTypes);
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"No matching factory method found: " +
(mbd.getFactoryBeanName() != null ?
"factory bean '" + mbd.getFactoryBeanName() + "'; " : "") +
"factory method '" + mbd.getFactoryMethodName() + "(" + argDesc + ")'. " +
"Check that a method with the specified name " +
(minNrOfArgs > 0 ? "and arguments " : "") +
"exists and that it is " +
(isStatic ? "static" : "non-static") + ".");
}
//返回类型为void,抛异常
else if (void.class.equals(factoryMethodToUse.getReturnType())) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Invalid factory method '" + mbd.getFactoryMethodName() +
"': needs to have a non-void return type!");
}
//当存在歧义函数的情况下,抛异常,(也就是3.4示例“非宽松模式测试”抛出的异常)
else if (ambiguousFactoryMethods != null) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Ambiguous factory method matches found in bean '" + beanName + "' " +
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
ambiguousFactoryMethods);
} if (explicitArgs == null && argsHolderToUse != null) {
argsHolderToUse.storeCache(mbd, factoryMethodToUse);
}
}
}

4.2 非宽松模式权重计算

public int getAssignabilityWeight(Class<?>[] paramTypes) {
for (int i = 0; i < paramTypes.length; i++) {
//父子类、接口实现类或基本数据类型返回true,arguments是转换后的参数,相对于rawArguments
if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
return Integer.MAX_VALUE;
}
}
for (int i = 0; i < paramTypes.length; i++) {
//与原始参数对比,父子类或原始类型返回ture,这里的rawArguments是原始参数
if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {
return Integer.MAX_VALUE - 512;
}
}
return Integer.MAX_VALUE - 1024;
}

非宽松模式的权重计算,重点是只要是父子类或实现类返回Ture,也就意味着这个函数在多个构造函数参数为父子类或实现类的关系时,会全部返回(Integer.MAX_VALUE - 1024),这样返回统一的数字相等,spring就会认为存在有歧义的函数,不能确定使用哪一个。

4.3 宽松模式权重计算

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++) {
//非父子或实现类或基本数据类型 返回 Integer.MAX_VALUE
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) {
//本身类 权重+2
if (paramType.equals(superClass)) {
result = result + 2;
superClass = null;
}
//父子、接口实现类 权重+2
else if (ClassUtils.isAssignable(paramType, superClass)) {
result = result + 2;
superClass = superClass.getSuperclass();
}
else {
superClass = null;
}
}
//是接口权重+1
if (paramType.isInterface()) {
result = result + 1;
}
}
}
return result;
}

看以上计算方式,宽松模式的权重计算,主要是查找最接近本身类型的那个函数。


五、结论

  1. lenientConstructorResolution属性的作用:确定构造函数是是否使用宽松构造的方式。

  2. lenientConstructorResolution的值ture与false有什么区别:这个属性默认值是true,在大部分情况下都是使用宽松模式,即使多个构造函数的参数数量相同、类型存在父子类、接口实现类关系也能正常创建bean。非宽松模式则相反,的使用场景还需要继续探索。

  3. 源码中是如何实现的,使用权重设计的目的是:第四小节为源码的实现,还有不足之处。至于使用权重的设计,在查看其他源码时,会发现,spring中大量使用位运算,权重计算等数值运算,我想这应该是数值运算类型效率最高的原因。

AbstractBeanDefinition:lenientConstructorResolution属性源码分析的更多相关文章

  1. Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

    1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...

  2. jQuery原型属性constructor,selector,length,jquery和原型方法size,get,toArray源码分析

    首先看一下在jQuery1.7.1中定义的原型属性和方法有哪些? init方法作为实际的构造函数已经详细分析过了,需要了解可以参考http://www.cnblogs.com/yy-hh/p/4492 ...

  3. Spring 源码分析之 bean 依赖注入原理(注入属性)

         最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...

  4. jQuery 源码分析(十三) 数据操作模块 DOM属性 详解

    jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...

  5. Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...

  6. vuex源码分析(二) state及strict属性 详解

    state也就是vuex里的值,也即是整个vuex的状态,而strict和state的设置有关,如果设置strict为true,那么不能直接修改state里的值,只能通过mutation来设置 例1: ...

  7. Vue.js 源码分析(四) 基础篇 响应式原理 data属性

    官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...

  8. Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解

    Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...

  9. Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解

    先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...

随机推荐

  1. jQuery源码分析--Event模块(3)

    最后剩下了事件的手动触发了.jQuery提供了两个函数trigger和triggerHandler来手动触发事件,可以触发原生事件和自定义的事件.这个触发不单只会触发有jQuery绑定事件,而且也会触 ...

  2. 20155201 2016-2017-2 《Java程序设计》第八周学习总结

    20155201 2016-2017-2 <Java程序设计>第八周学习总结 教材学习内容总结 第十四章 NIO与NIO2 相对于串流输入/输出使用InputSteam,OutputStr ...

  3. phpstorm2016.1 添加对Drupal的编程支持

    一.前言 phpstorm作为目前对drupal支持最好的开发工具之一,是drupal模块开发的首选工具.今天我就来谈谈最新的phpstorm如何添加对drupal模块的支持. 相关环境:操作系统ub ...

  4. JQuery实现锚点平滑滚动

    一般使用锚点来跳转到页面指定位置的时候,会生硬地立即跳转到指定位置,但是有些时候我们想要平滑地过渡到指定的位置,那么可以使用JQuery简单的实现这个效果: 比如,这里我们将通过点击<a> ...

  5. sqlite中的时间

    插入时间的sql语句 ','-61') 时间格式'2014-11-17T19:37:32' 年月日和时分秒之间多了一个字母T,保存到数据库的时候,会自动给时间加8个小时. 保存的结果为2014-11- ...

  6. Python os.system()调用.sh脚本

    参考: python调用shell脚本的两种方法| Jeff的妙想奇境 已解决--求教python如何调用.sh文件- 查看主题• Ubuntu中文论坛 CODE #!/usr/bin/env pyt ...

  7. Gym 101246G Revolutionary Roads

    http://codeforces.com/gym/101246/problem/G 题意: 给出一个有向图,现在可以把图中的任意一条边改为无向边,问强连通分量最多可以有多少个点,在此情况下输出所有能 ...

  8. LA 7277 Landscaping(最小割)

    https://vjudge.net/problem/UVALive-7277 题意: 给出一个n*m的地图,.代表低坡,#代表高坡. 现在有n+m辆车分别从上端和左端出发,如果在行驶的过程中需要转换 ...

  9. 山东省第四届ACM程序设计竞赛部分题解

    A : Rescue The Princess 题意: 给你平面上的两个点A,B,求点C使得A,B,C逆时针成等边三角形. 思路: http://www.cnblogs.com/E-star/arch ...

  10. ResourceNotFound: rosbridge_server

    Checking log directory for disk usage. This may take awhile. Press Ctrl-C to interrupt Done checking ...