AbstractBeanDefinition:lenientConstructorResolution属性源码分析
版本:spring-framework-4.1
一概述
在看AbstractBeanDefinition源码时,注意到lenientConstructorResolution属性有诸多不疑,现在通过示例及源码分析,一步步揭开面纱。
二、本文希望能解释清楚的几个问题
- lenientConstructorResolution属性的作用?
- lenientConstructorResolution的值ture与false有什么区别?
- 源码中是如何实现的,为何使用权重设计?
三、示例
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;
}
看以上计算方式,宽松模式的权重计算,主要是查找最接近本身类型的那个函数。
五、结论
lenientConstructorResolution属性的作用:确定构造函数是是否使用宽松构造的方式。
lenientConstructorResolution的值ture与false有什么区别:这个属性默认值是true,在大部分情况下都是使用宽松模式,即使多个构造函数的参数数量相同、类型存在父子类、接口实现类关系也能正常创建bean。非宽松模式则相反,的使用场景还需要继续探索。
源码中是如何实现的,使用权重设计的目的是:第四小节为源码的实现,还有不足之处。至于使用权重的设计,在查看其他源码时,会发现,spring中大量使用位运算,权重计算等数值运算,我想这应该是数值运算类型效率最高的原因。
AbstractBeanDefinition:lenientConstructorResolution属性源码分析的更多相关文章
- Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...
- jQuery原型属性constructor,selector,length,jquery和原型方法size,get,toArray源码分析
首先看一下在jQuery1.7.1中定义的原型属性和方法有哪些? init方法作为实际的构造函数已经详细分析过了,需要了解可以参考http://www.cnblogs.com/yy-hh/p/4492 ...
- Spring 源码分析之 bean 依赖注入原理(注入属性)
最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...
- jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...
- Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...
- vuex源码分析(二) state及strict属性 详解
state也就是vuex里的值,也即是整个vuex的状态,而strict和state的设置有关,如果设置strict为true,那么不能直接修改state里的值,只能通过mutation来设置 例1: ...
- Vue.js 源码分析(四) 基础篇 响应式原理 data属性
官网对data属性的介绍如下: 意思就是:data保存着Vue实例里用到的数据,Vue会修改data里的每个属性的访问控制器属性,当访问每个属性时会访问对应的get方法,修改属性时会执行对应的set方 ...
- Vue.js 源码分析(三) 基础篇 模板渲染 el、emplate、render属性详解
Vue有三个属性和模板有关,官网上是这样解释的: el ;提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标 template ;一个字符串模板作为 Vue 实例的标识使用.模板将会 ...
- Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解
先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...
随机推荐
- centos6.5/6.6配置java环境以及数据库
配置java环境 一.解压jdk 二.配置环境变量 1.修改修改/etc/profile文件(推荐开发环境使用,因为所有用户shell都有权使用这些环境变量,可能带来环境问题) 在profile末尾加 ...
- windows下注册tomcat服务以及设置jvm参数
注册服务: 1 >cd /d D:\Java\tomcat-7.0.57-Css\bin //进入目录 1 >service.bat install //注册服务,同理删除服务为 rem ...
- Mysql优化原则_小表驱动大表IN和EXISTS的合理利用
//假设一个for循环 ; $i < ; $i++) { ; $i < ; $j++) { } } ; $i < ; $i++) { ; $i < ; $j++) { } } ...
- uboot 版本号生成过程
uboot 版本号生成过程 uboot版本号貌似与实际开发不相关,但是我现在遇到一个bug与版本号关联密切. 这个bug与<uboot dm9000驱动故障>基本上是一样的,但是在上一篇博 ...
- cuda8.0 出错:/usr/bin/ld: 找不到 -lGL【转】
本文转自:https://blog.csdn.net/u010159842/article/details/56833030 最近在学习深度学习,在搭建CUDA8.0的时候,出现一个非常不好的问题: ...
- bzoj 1799: [Ahoi2009]self 同类分布 数位dp
1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec Memory Limit: 64 MB[Submit][Status][Discuss] Descripti ...
- $ocLazyLoad
博客:http://zhidao.baidu.com/link?url=1eODexxXPsl2gy4UsRnfIqPJnzFrzFk2JJad-cjWDiyCKkb4qxS8scvxoMRqM0Fw ...
- Hive 建外链表到 Hbase(分内部表、外部表两种方式)
一. Hive 建内部表,链到hbase :特点:Hive drop表后,Hbase 表同步删除 drop table if exists hbase_kimbo_test1;CREATE TABLE ...
- vim with space-vim
space-vim https://github.com/liuchengxu/space-vim macOS # homebrew /usr/bin/ruby -e "$(curl -fs ...
- Eclips安装SVN插件
1.eclipse -> Help ->Install New Software->add 2.弹出窗的"Name"和"URL"中输入如下内容 ...