版本: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. Java实现使用位图生成真值组合

    摘要: 使用位图生成真值组合. 难度: 初级.   /** * 问题描述: 给定 n 个布尔变量,打印所有真值组合. * 例如, n = 2 时 , 所有真值组合为 (true, false),(tr ...

  2. SQL sqlserver order by 1,order by 后面直接加数字,多个字段排序

    ①select * from table order by n 表示select里面的第n个字段 ②多个字段排序

  3. Vue学习笔记之Vue的面向对象

    0x00 准备工作 JavaScript 语言中,生成实例对象的传统方法是通过构造函数. function Animal(name,age){ this.name = name; this.age = ...

  4. @Tranactional事务没有回滚

    一.特性 先来了解一下@Transactional注解事务的特性吧,可以更好排查问题 1.service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务 ...

  5. Spark 任务提交脚本

    说明 该脚本是根据输入起始日期-结束日期,执行从数据库拉取日期间隔数据到HDFS.日期间隔中的日期就是每一年的自然日. 日期格式可以是以下几种:2018-01-01 2018-12-31 [-][/] ...

  6. asp.net调用opencv类库,实现图像处理显示

    asp.net调用opencv类库,实现图像处理显示     ​      原理上来说,通过dll的调用,无论是asp.net还是winform都可以调用opencv及其类库.但是在实现的过程还是有许 ...

  7. [转][修]sprintf()函数:将格式化的数据写入字符串

    头文件:#include <stdio.h>功能:用于将格式化的数据写入字符串 原型:int sprintf(char *str, char * format [, argument, . ...

  8. install ros-indigo-map-msgs

    CMake Warning at /opt/ros/indigo/share/catkin/cmake/catkinConfig.cmake: (find_package): Could not fi ...

  9. python 获取昨天今天明天日期

    import datetime today = datetime.date.today() yesterday = today - datetime.timedelta(days = ) tomorr ...

  10. iframe与父页面传值

    最近做的项目中用到了不少iframe,而且需要实现: 从父页面获取iframe中某个元素的值或则从iframe页面获取其父页面上某个元素的值. 在网上查询了相关东西,后总结如下: (1)父页面获取if ...