上一节我们分析了Spring通过默认构造函数实例化bean的过程,本小节分析Spring使用有参构造函数实例化bean的过程。
引
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 确保此时beanClass已经被解析
Class<?> beanClass = resolveBeanClass(mbd, beanName);
// beanClass不为空,且beanClass的修饰符为不为public,且不允许访问非公共构造函数和方法,则抛出异常
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());
}
// ① Spring5.0新增的实例化策略,如果设置了该策略,将会覆盖构造方法和工厂方法实例化策略
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
// ② 如果有工厂方法的话,则使用工厂方法实例化bean
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// ③ 当创建一个相同的bean时,使用之间保存的快照
// 这里可能会有一个疑问,什么时候会创建相同的bean呢?
// ③-->① 单例模式: Spring不会缓存该模式的实例,那么对于单例模式的bean,什么时候会用到该实例化策略呢?
// 我们知道对于IoC容器除了可以索取bean之外,还能销毁bean,当我们调用xmlBeanFactory.destroyBean(myBeanName,myBeanInstance),
// 销毁bean时,容器是不会销毁已经解析的构造函数快照的,如果再次调用xmlBeanFactory.getBean(myBeanName)时,就会使用该策略了.
// ③-->② 原型模式: 对于该模式的理解就简单了,IoC容器不会缓存原型模式bean的实例,当我们第二次向容器索取同一个bean时,就会使用该策略了.
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
// 如果该bean已经被解析过
if (resolved) {
// 使用已经解析过的构造函数实例化
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
// 使用默认无参构造函数实例化
else {
return instantiateBean(beanName, mbd);
}
}
// ④ 确定需要使用的构造函数
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// ⑤ 无任何的特殊处理,则使用默认的无参构造函数实例化bean
return instantiateBean(beanName, mbd);
}
从代码中可以看到,如果该bean的构造函数已经被解析并缓存的话,则优先使用已经被解析的构造函数实例化,否则解析bean的构造函数并实例化。
在这里我们再一次看到了Spring对缓存的应用,而且解析构造函数是一个比较复杂耗时的操作,所以这里需要缓存已经解析的构造函数。
1.测试用例
打开day01下的MyTest类:
@Test
public void test2() {
// 指定构造器
System.out.println("有参构造器");
Dog dog2 = xmlBeanFactory.getBean("dog2", Dog.class);
dog2.sayHello();
}
2.解析构造函数
- 创建ConstructorResolver对象
protected BeanWrapper autowireConstructor(
String beanName,
RootBeanDefinition mbd,
@Nullable Constructor<?>[] ctors,
@Nullable Object[] explicitArgs) {
return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
- 解析构造函数
public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
@Nullable Constructor<?>[] chosenCtors, @Nullable final Object[] explicitArgs) {
BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw);
Constructor<?> constructorToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;
// 1、 判断有无显式指定参数,如果有则优先使用,如xmlBeanFactory.getBean("cat", "美美",3);
if (explicitArgs != null) {
argsToUse = explicitArgs;
}
// 2、 没有显式指定参数,则解析配置文件中的参数
else {
Object[] argsToResolve = null;
// 3、 优先尝试从缓存中获取,spring对参数的解析过程是比较复杂也耗时的,所以这里先尝试从缓存中获取已经解析过的构造函数参数
synchronized (mbd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
if (constructorToUse != null && mbd.constructorArgumentsResolved) {
// Found a cached constructor...
argsToUse = mbd.resolvedConstructorArguments;
if (argsToUse == null) {
argsToResolve = mbd.preparedConstructorArguments;
}
}
}
// 缓存中存在,则解析构造函数参数类型
if (argsToResolve != null) {
argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
}
}
// 4、 缓存中不存在,则需要解析构造函数参数,以确定使用哪一个构造函数来进行实例化
if (constructorToUse == null) {
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.
// 5、 使用指定的构造函数(如果有的话)。
// 注意: 5.1、 这里说的指定构造函数,并不是我们在配置文件中指定的构造函数,而是通过解析SmartInstantiationAwareBeanPostProcessor得出的构造函数
// 参见AbstractAutowireCapableBeanFactory-->determineConstructorsFromBeanPostProcessors(beanClass, beanName),
// 就是在本方法被调用之前执行的解析操作
// 5.2、 即便解析出来的构造函数不为空,但是大家要注意,candidates是个数组,下一步依然还是要对candidates进行解析,以确定使用哪一个构造函数进行实例化
Constructor<?>[] candidates = chosenCtors;
if (candidates == null) {
Class<?> beanClass = mbd.getBeanClass();
try {
// 6、 如果指定的构造函数不存在,则根据方法访问级别,获取该bean所有的构造函数
// 对于本例来分析,应该会获取到四个构造函数Cat(),Cat(String name),Cat(int age),Cat(String name, int age)
// 注意:该处获取到的构造函数,并不是配置文件中定义的构造函数,而是bean类中的构造函数
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);
}
}
// 7、 对构造函数按照参数个数和参数类型进行排序,参数最多的构造函数会排在第一位
AutowireUtils.sortConstructors(candidates);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null;
// 8、 循环所有bean类中的构造函数,解析确定使用哪一个构造函数
for (Constructor<?> candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
// 如果constructorToUse不为空,且参数个数大于当前循环的构造函数参数个数,则直接终止循环,因为解析的bean类构造函数已经经过排序
// 问题: 进入该if语句的条件是constructorToUse==null? 该处判断是否多余.. ?
// 带着这个问题,可以接着看源码...该处的设计还是值得参考的
if (constructorToUse != null && argsToUse.length > paramTypes.length) {
break;
}
// 如果从bean类中解析到的构造函数个数小于从beanDefinition中解析到的构造函数个数,
// 那么肯定不会使用该方法实例化,循环继续
// 简单的理解:beanDefinition中的构造函数和bean类中的构造函数参数个数不相等,那么肯定不会使用该构造函数实例化
if (paramTypes.length < minNrOfArgs) {
continue;
}
// 9、 获取ArgumentsHolder对象,直接理解为一个参数持有者即可
ArgumentsHolder argsHolder;
if (resolvedValues != null) {
try {
String[] paramNames = 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,
getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
}
catch (UnsatisfiedDependencyException ex) {
if (logger.isTraceEnabled()) {
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 ArgumentsHolder(explicitArgs);
}
// 10、 通过构造函数参数权重对比,得出最适合使用的构造函数
// 先判断是返回是在宽松模式下解析构造函数还是在严格模式下解析构造函数。(默认是宽松模式)
// 10.1、对于宽松模式:例如构造函数为(String name,int age),配置文件中定义(value="美美",value="3")
// 那么对于age来讲,配置文件中的"3",可以被解析为int也可以被解析为String,
// 这个时候就需要来判断参数的权重,使用ConstructorResolver的静态内部类ArgumentsHolder分别对字符型和数字型的参数做权重判断
// 权重越小,则说明构造函数越匹配
// 10.2、对于严格模式:严格返回权重值,不会根据分别比较而返回比对值
// 10.3、minTypeDiffWeight = Integer.MAX_VALUE;而权重比较返回结果都是在Integer.MAX_VALUE做减法,起返回最大值为Integer.MAX_VALUE
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this constructor if it represents the closest match.
// 如果此构造函数表示最接近的匹配,则选择此构造函数
if (typeDiffWeight < minTypeDiffWeight) {
// 将解析到的构造函数赋予constructorToUse,这也是我们上面问题疑问的答案所在处
constructorToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousConstructors = null;
}
else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
if (ambiguousConstructors == null) {
ambiguousConstructors = new LinkedHashSet<>();
ambiguousConstructors.add(constructorToUse);
}
ambiguousConstructors.add(candidate);
}
}
// 11、 异常处理
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);
}
// 12、 缓存解析的构造函数
if (explicitArgs == null) {
argsHolderToUse.storeCache(mbd, constructorToUse);
}
}
try {
// 13、 获取实例化策略并执行实例化
final InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy();
Object beanInstance;
if (System.getSecurityManager() != null) {
final Constructor<?> ctorToUse = constructorToUse;
final Object[] argumentsToUse = argsToUse;
beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
strategy.instantiate(mbd, beanName, this.beanFactory, ctorToUse, argumentsToUse),
this.beanFactory.getAccessControlContext());
}
else {
beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
// 14、 返回BeanWrapper包装类
bw.setBeanInstance(beanInstance);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via constructor failed", ex);
}
}
这个过程是相当复杂的,逐步分析其过程:
2.1 判断有无显式指定参数,如果有则优先使用,如xmlBeanFactory.getBean("cat", "美美",3);
2.2 优先尝试从缓存中获取,spring对参数的解析过程是比较复杂也耗时的,所以这里先尝试从缓存中获取已经解析过的构造函数参数
2.3 缓存中不存在,则需要解析构造函数参数,以确定使用哪一个构造函数来进行实例化
一个类可能存在多个构造函数,如Dog(String name,int age);Dog(String name);Dog(int age)等等,所以需要解析对构造函数进行解析,以确定使用哪一个构造函数。
2.4 使用指定的构造函数(如果有的话)。
这里说的指定构造函数,并不是我们在配置文件中指定的构造函数,而是通过解析SmartInstantiationAwareBeanPostProcessor得出的构造函数。
参见AbstractAutowireCapableBeanFactory-->determineConstructorsFromBeanPostProcessors(beanClass, beanName),
就是在本方法被调用之前执行的解析操作,即便解析出来的构造函数不为空,但是大家要注意,candidates是个数组,下一步依然还是要对candidates进行解析,以确定使用哪一个构造函数进行实例化。
2.5 如果指定的构造函数不存在,则根据方法访问级别,获取该bean所有的构造函数
需要注意,该步骤获取的是类的构造函数,而不是在配置文件中的构造函数。
2.6 对构造函数按照参数个数和参数类型进行排序,参数最多的构造函数会排在第一位
2.7 循环所有bean类中的构造函数,解析确定使用哪一个构造函数
首先因为构造函数已经按照参数的个数排序,参数个数最多的排在最前面,所以判断如若解析出来的构造函数个数小于BeanDefinition中的构造函数个数,那么肯定不会使用该构造函数进行实例化,那么循环会继续。
其次将解析到的构造函数封装至ArgumentsHolder对象。
最后通过构造函数参数权重对比,得出最适合使用的构造函数。
2.8 处理异常,缓存解析过的构造函数。
2.9 获取实例化策略并执行实例化
同样这里也会有反射或CGLIB实例化bean,具体的细节,上一节已经分析过。
2.10 返回BeanWrapper包装类
3.总结
本节的重点和难点在于对构造函数的确定,大家可以多定义一些有参构造函数,多通过调试来加深理解。
网友评论