欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring推断构造函数中自动装配构造函数过程里的构造函数参数权重计算

程序员文章站 2022-05-24 16:11:32
...

Spring推断构造函数中自动装配构造函数过程里的构造函数参数权重计算

我们知道,在Spring的refresh()步骤中进行单例bean的初始化,在bean的初始化过程中,也就涉及到进行最合适的构造函数的推断,本文也将介绍构造函数推断中,进行构造函数参数权重计算的部分,详细的代码位置位于方法:
org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor(String beanName, RootBeanDefinition mbd,@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs)

代码说明

在方法中,进行权重比较的代码为

int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));

这句话的含义在为:如果在bean定义中设置构造函数的解析为宽松模式,则使用getTypeDifferenceWeight计算权重,否则使用getAssignabilityWeight计算权重。构造函数解析的默认为宽松模式

由此,我们在此介绍getTypeDifferenceWeightgetAssignabilityWeight的两种不同的权重计算方式

getTypeDifferenceWeight 根据参数对象类型同参数类型的差得到权重

/**
 *得到参数同参数类型的差异权重
 * @param paramTypes 参数类型集合
 * @return 权重差值
 */
public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
	// 如果找到有效的参数,请确定类型差异权重。
	// 尝试在转换后的参数和原始参数上键入不同的权重。
	// 如果原始参数权重更好,使用它。
	// 将原始参数权重减少1024,使其优于同等的转换权重。

	// 获取参数值同参数类型比较之后的权重
	int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
	// 获取原参数值同参数类型比较之后的权重,并减去1024,标识优先使用原参数
	int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
	// 比较得到最优的权重差值
	return Math.min(rawTypeDiffWeight, typeDiffWeight);
}

这个方法中调用了两次MethodInvoker.getTypeDifferenceWeight分别计算转换之后的参数对象同参数类型比较的权重和原对象类型同参数类型比较的权重,并且在原对象比较权重-1024的基础上同转换后的参数对象比较权重相比,取最优的权重差值。

下面我们看下这个用于进行关键权重计算的方法MethodInvoker.getTypeDifferenceWeight

/**
 * 判断候选方法的声明参数类型与该方法应该使用的特定参数列表之间的匹配的算法。
 * <p>确定表示类型和参数之间的类层次结构差异的权重。
 * 直接匹配,即类型为Integer -> 类Integer的参数,不会增加结果 — 所有直接匹配意味着权重为0。
 * 类型Object和类Integer的arg之间的匹配将增加2的权重,因为层次结构中的向上2步的超类(即,Object)是最后一个仍然匹配所需类型对象的。
 * 类型数量和类整数Integer将增加1权重,因此,由于层次结构中的向上1步的超类加强(即数字)仍然匹配所需的型号,一个整数类型的参数,
 * 构造函数(Integer)会倾向于一个构造函数(Number),反过来会倾向于构造函数(Object)。所有的参数权值都被累加。
 * <p>Determines a weight that represents the class hierarchy difference between types and
 * arguments. A direct match, i.e. type Integer -> arg of class Integer, does not increase
 * the result - all direct matches means weight 0. A match between type Object and arg of
 * class Integer would increase the weight by 2, due to the superclass 2 steps up in the
 * hierarchy (i.e. Object) being the last one that still matches the required type Object.
 * Type Number and class Integer would increase the weight by 1 accordingly, due to the
 * superclass 1 step up the hierarchy (i.e. Number) still matching the required type Number.
 * Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a
 * constructor (Number) which would in turn be preferred to a constructor (Object).
 * All argument weights get accumulated.
 *
 * <p>注意:这是MethodInvoker本身使用的算法,也是Spring bean容器中用于构造函数和工厂方法选择的算法(如果构造函数解析很宽松,这是常规bean定义的默认解析)。
 * @param paramTypes 要匹配的参数类型
 * @param args 要匹配的参数
 * @return 所有参数的累计权重
 */
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();
			// 循环比较参数的父类,如果参数的父类同参数类型一致,则权重+2;
			// 如果参数的父类同参数类型不一致,但是父类是参数类型的父类或实现类,则权重+2,继续获取父类的父类,进行参数类型的比较;
			// 直至参数类型的父类同参数类型不一致,且不是参数类型的子类或实现类
			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. 如果给定的参数对象类型不是继承或实现自对应的参数类型,则返回Integer.MAX_VALUE
  2. 如果给定的参数对象类型的父类同对应的参数类型一致,则权重+2
  3. 如果给定的参数对象类型的父类继承或实现自给定的参数类型,则权重+2,并且获取父类的父类,继续循环判断
  4. 在父类循环判断完成后,如果判断对应的参数类型是一个接口,则权重+1
  5. 最终返回所有参数判断完成后的权重值

举例:

已知:

public Constructor1(Object obj,Integer i,UserDao str){}

Integer extends Number extends Object
UserDaoImpl implements UserDao

给定参数

{
new Integer(1),
new Integer(1),
new UserDaoImpl()
}

权重计算过程为:

  1. 先比较第一个参数:
    • 参数类型为Object,给定参数对象类型为Integer,继承自Object;
    • 给定参数对象类型为Integer,父类为Number,继承自Object权重+2当前权重为2
    • 父类Number的父类为Object,同参数类型相同,权重+2当前权重为4,结束循环
    • 参数类型为Object不是接口,第一个参数权重计算完毕
  2. 比较第二个参数
    • 参数类型为Integer,给定参数类型为Integer,符合ClassUtils.isAssignableValue的判断条件
    • 给定参数对象类型为Integer,父类为Number,并非继承或实现自Integer,结束循环,当前权重为4
    • 参数类型Integer不是接口,第二个参数权重计算完毕
  3. 比较第三个参数
    • 参数类型为UserDao,给定参数对象类型为UserDaoImpl,实现自UserDao;
    • 给定参数对象类型为UserDaoImpl,父类为Object,并非继承或实现自UserDao,结束循环,当前权重为4
    • 参数类型UserDao是一个接口,权重+1当前权重为5,第三个参数权重计算完毕

最终得到的权重值为:5

getAssignabilityWeight 根据继承或实现关系计算权重

在严格模式下进行构造函数解析时,会根据继承或实现关系计算权重值,最终会调用getAssignabilityWeight进行权重计算,逻辑如下:

/**
 * 得到可转换权重
 * @param paramTypes 参数类型
 * @return 返回权重差值
 */
public int getAssignabilityWeight(Class<?>[] paramTypes) {
	// 遍历参数类型,如果转换后的参数对象类型不是继承或实现自参数类型,则返回最大整数
	for (int i = 0; i < paramTypes.length; i++) {
		if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
			return Integer.MAX_VALUE;
		}
	}
	// 遍历参数类型,如果原参数对象类型不是继承或实现自参数类型,则返回最大值-512
	for (int i = 0; i < paramTypes.length; i++) {
		if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {
			return Integer.MAX_VALUE - 512;
		}
	}
	// 如果转换后的参数对象类型和原参数对象类型均是继承或实现自对应的参数类型,则返回最大值-1024
	return Integer.MAX_VALUE - 1024;
}

这个方法中的判断很简单,

  • 首先循环判断转换后的参数值对象,如果存在参数值对象的类型不是等于、继承或实现自对应的参数类型,直接返回Integer.MAX_VALUE
  • 然后循环判断原参数值对象,如果存在参数值对象的类型不是等于、继承或实现自对应的参数类型,直接返回Integer.MAX_VALUE - 512
  • 否则返回Integer.MAX_VALUE - 1024

宽松模式 VS 严格模式

在此我们通过代码比较自动装配构造函数解析模式中的宽松模式和严格模式的比较,如下:

假定我们有一个bean类

package com.chenss.dao;

import org.springframework.stereotype.Component;

@Component
public class TaggerDao {
	public TaggerDao(UserDao userDao) {
		System.out.println("TaggerDao UserDao");
	}

	public TaggerDao(UserDaoImpl userDao) {
		System.out.println("TaggerDao UserDaoImpl");
	}
}

其中UserDao是一个接口,UserDaoImplUserDao的实现类

package com.chenss.dao;

public interface UserDao {
	void query();
}

@Repository("userDao")
public class UserDaoImpl implements UserDao {
}

然后我们通过在bean工厂的后置处理器中修改bean定义中的装配模式和自动装配构造函数解析方式

@Component
public class ChenssBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
		GenericBeanDefinition taggerDao = (GenericBeanDefinition) defaultListableBeanFactory.getBeanDefinition("taggerDao");
		taggerDao.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
		taggerDao.setLenientConstructorResolution(true); // 宽松模式
		//taggerDao.setLenientConstructorResolution(true);// 严格模式
	}
}

在宽松模式下的执行结果为:

TaggerDao userDao

而在严格模式下的执行结果为:

十一月 13, 2019 3:18:38 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taggerDao' defined in file [D:\Java\spring-framework-master\chenss-spring\out\production\classes\com\chenss\dao\TaggerDao.class]: Ambiguous constructor matches found in bean 'taggerDao' (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): [public com.chenss.dao.TaggerDao(com.chenss.dao.UserDao), public com.chenss.dao.TaggerDao(com.chenss.dao.UserDaoImpl)]
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'taggerDao' defined in file [D:\Java\spring-framework-master\chenss-spring\out\production\classes\com\chenss\dao\TaggerDao.class]: Ambiguous constructor matches found in bean 'taggerDao' (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): [public com.chenss.dao.TaggerDao(com.chenss.dao.UserDao), public com.chenss.dao.TaggerDao(com.chenss.dao.UserDaoImpl)]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:325)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1353)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1199)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:548)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:506)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:328)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:240)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:325)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:893)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:896)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:555)
	at com.chenss.test.Test.main(Test.java:21)

总结

进行构造函数权重计算的逻辑并不复杂,通过权重计算的不同方式,我们可以做出如下判断:
宽松模式下,权重计算更为精细,也更能找到最适合的构造函数;
而严格模式下,因为比较粒度较大,如果存在有两个或以上构造函数,且参数分别为子类、子类的父类或父接口的类在进行自动注入构造函数解析时,更容易因为找到模棱两可的构造函数而抛出异常。