摘要:你也会了解到构造对象的两种策略。构造方法参数数量低于配置的参数数量,则忽略当前构造方法,并重试。通过默认构造方法创建对象看完了上面冗长的逻辑,本节来看点轻松的吧通过默认构造方法创建对象。
1. 简介
本篇文章是上一篇文章(创建单例 bean 的过程)的延续。在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程。本篇文章,我们就从战术的层面上,详细分析doCreateBean方法中的一个重要的调用,即createBeanInstance方法。在本篇文章中,你将看到三种不同的构造 bean 对象的方式。你也会了解到构造 bean 对象的两种策略。如果你对这些内容感兴趣,那么不妨继续往下读。我会在代码进行大量的注解,相信能帮助你理解代码逻辑。好了,其他的就不多说了,进入正题吧。
2. 源码分析 2.1 创建 bean 对象的过程本节,我们一起来来分析一下本篇文章的主角createBeanInstance方法。按照惯例,我们还是先分析一下方法的大致脉络,然后我们再按照这个脉络去分析一些重要的调用。So. Let`s go → ↓
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { Class> beanClass = resolveBeanClass(mbd, beanName); /* * 检测类的访问权限。默认情况下,对于非 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()); } /* * 如果工厂方法不为空,则通过工厂方法构建 bean 对象。这种构建 bean 的方式 * 就不深入分析了,有兴趣的朋友可以自己去看一下。 */ if (mbd.getFactoryMethodName() != null) { // 通过“工厂方法”的方式构建 bean 对象 return instantiateUsingFactoryMethod(beanName, mbd, args); } /* * 当多次构建同一个 bean 时,可以使用此处的快捷路径,即无需再次推断应该使用哪种方式构造实例, * 以提高效率。比如在多次构建同一个 prototype 类型的 bean 时,就可以走此处的捷径。 * 这里的 resolved 和 mbd.constructorArgumentsResolved 将会在 bean 第一次实例 * 化的过程中被设置,在后面的源码中会分析到,先继续往下看。 */ boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { // 通过“构造方法自动注入”的方式构造 bean 对象 return autowireConstructor(beanName, mbd, null, null); } else { // 通过“默认构造方法”的方式构造 bean 对象 return instantiateBean(beanName, mbd); } } // 由后置处理器决定返回哪些构造方法,这里不深入分析了 Constructor>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); /* * 下面的条件分支条件用于判断使用什么方式构造 bean 实例,有两种方式可选 - 构造方法自动 * 注入和默认构造方法。判断的条件由4部分综合而成,如下: * * 条件1:ctors != null -> 后置处理器返回构造方法数组是否为空 * * 条件2:mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR * -> bean 配置中的 autowire 属性是否为 constructor * 条件3:mbd.hasConstructorArgumentValues() * -> constructorArgumentValues 是否存在元素,即 bean 配置文件中 * 是否配置了* 条件4:!ObjectUtils.isEmpty(args) * -> args 数组是否存在元素,args 是由用户调用 * getBean(String name, Object... args) 传入的 * * 上面4个条件,只要有一个为 true,就会通过构造方法自动注入的方式构造 bean 实例 */ if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { // 通过“构造方法自动注入”的方式构造 bean 对象 return autowireConstructor(beanName, mbd, ctors, args); } // 通过“默认构造方法”的方式构造 bean 对象 return instantiateBean(beanName, mbd); }
以上就是 createBeanInstance 方法的源码,不是很长。配合着注释,应该不是很难懂。下面我们来总结一下这个方法的执行流程,如下:
检测类的访问权限,若禁止访问,则抛出异常
若工厂方法不为空,则通过工厂方法构建 bean 对象,并返回结果
若构造方式已解析过,则走快捷路径构建 bean 对象,并返回结果
如第三步不满足,则通过组合条件决定使用哪种方式构建 bean 对象
这里有三种构造 bean 对象的方式,如下:
通过“工厂方法”的方式构造 bean 对象
通过“构造方法自动注入”的方式构造 bean 对象
通过“默认构造方法”的方式构造 bean 对象
下面我将会分析第2和第3种构造 bean 对象方式的实现源码。至于第1种方式,实现逻辑和第2种方式较为相似。所以就不分析了,大家有兴趣可以自己看一下。
2.2 通过构造方法自动注入的方式创建 bean 实例本节,我将会分析构造方法自动注入的实现逻辑。代码逻辑较为复杂,需要大家耐心阅读。代码如下:
protected BeanWrapper autowireConstructor( String beanName, RootBeanDefinition mbd, Constructor>[] ctors, Object[] explicitArgs) { // 创建 ConstructorResolver 对象,并调用其 autowireConstructor 方法 return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); } public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd, Constructor>[] chosenCtors, final Object[] explicitArgs) { // 创建 BeanWrapperImpl 对象 BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); Constructor> constructorToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; // 确定参数值列表(argsToUse) if (explicitArgs != null) { argsToUse = explicitArgs; } else { Object[] argsToResolve = null; synchronized (mbd.constructorArgumentLock) { // 获取已解析的构造方法 constructorToUse = (Constructor>) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // 获取已解析的构造方法参数列表 argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { // 若 argsToUse 为空,则获取未解析的构造方法参数列表 argsToResolve = mbd.preparedConstructorArguments; } } } if (argsToResolve != null) { // 解析参数列表 argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); } } 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 = maxIndex + 1 = 2 + 1 = 3,除了计算 minNrOfArgs, * 下面的方法还会将 cargs 中的参数数据转存到 resolvedValues 中 */ minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } // 获取构造方法列表 Constructor>[] candidates = chosenCtors; if (candidates == null) { Class> beanClass = mbd.getBeanClass(); try { 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); } } // 按照构造方法的访问权限级别和参数数量进行排序 AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; Set* * * > ambiguousConstructors = null; LinkedList causes = null; for (Constructor> candidate : candidates) { Class>[] paramTypes = candidate.getParameterTypes(); /* * 下面的 if 分支的用途是:若匹配到到合适的构造方法了,提前结束 for 循环 * constructorToUse != null 这个条件比较好理解,下面分析一下条件 argsToUse.length > paramTypes.length: * 前面说到 AutowireUtils.sortConstructors(candidates) 用于对构造方法进行 * 排序,排序规则如下: * 1. 具有 public 访问权限的构造方法排在非 public 构造方法前 * 2. 参数数量多的构造方法排在前面 * * 假设现在有一组构造方法按照上面的排序规则进行排序,排序结果如下(省略参数名称): * * 1. public Hello(Object, Object, Object) * 2. public Hello(Object, Object) * 3. public Hello(Object) * 4. protected Hello(Integer, Object, Object, Object) * 5. protected Hello(Integer, Object, Object) * 6. protected Hello(Integer, Object) * * argsToUse = [num1, obj2],可以匹配上的构造方法2和构造方法6。由于构造方法2有 * 更高的访问权限,所以没理由不选他(尽管后者在参数类型上更加匹配)。由于构造方法3 * 参数数量 < argsToUse.length,参数数量上不匹配,也不应该选。所以 * argsToUse.length > paramTypes.length 这个条件用途是:在条件 * constructorToUse != null 成立的情况下,通过判断参数数量与参数值数量 * (argsToUse.length)是否一致,来决定是否提前终止构造方法匹配逻辑。 */ if (constructorToUse != null && argsToUse.length > paramTypes.length) { break; } /* * 构造方法参数数量低于配置的参数数量,则忽略当前构造方法,并重试。比如 * argsToUse = [obj1, obj2, obj3, obj4],上面的构造方法列表中, * 构造方法1、2和3显然不是合适选择,忽略之。 */ if (paramTypes.length < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; if (resolvedValues != null) { try { /* * 判断否则方法是否有 ConstructorProperties 注解,若有,则取注解中的 * 值。比如下面的代码: * * public class Persion { * private String name; * private Integer age; * * @ConstructorProperties(value = {"coolblog", "20"}) * public Persion(String name, Integer age) { * this.name = name; * this.age = age; * } * } */ String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length); if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { /* * 获取构造方法参数名称列表,比如有这样一个构造方法: * public Person(String name, int age, String sex) * * 调用 getParameterNames 方法返回 paramNames = [name, age, sex] */ paramNames = pnd.getParameterNames(candidate); } } /* * 创建参数值列表,返回 argsHolder 会包含进行类型转换后的参数值,比如下 * 面的配置: * * * * * Person 的成员变量 age 是 Integer 类型的,但由于在 Spring 配置中 * 只能配成 String 类型,所以这里要进行类型转换。 */ argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring); } catch (UnsatisfiedDependencyException ex) { if (this.beanFactory.logger.isTraceEnabled()) { this.beanFactory.logger.trace( "Ignoring constructor [" + candidate + "] of bean "" + beanName + "": " + ex); } if (causes == null) { causes = new LinkedList* * * (); } causes.add(ex); continue; } } else { if (paramTypes.length != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } /* * 计算参数值(argsHolder.arguments)每个参数类型与构造方法参数列表 * (paramTypes)中参数的类型差异量,差异量越大表明参数类型差异越大。参数类型差异 * 越大,表明当前构造方法并不是一个最合适的候选项。引入差异量(typeDiffWeight) * 变量目的:是将候选构造方法的参数列表类型与参数值列表类型的差异进行量化,通过量化 * 后的数值筛选出最合适的构造方法。 * * 讲完差异量,再来说说 mbd.isLenientConstructorResolution() 条件。 * 官方的解释是:返回构造方法的解析模式,有宽松模式(lenient mode)和严格模式 * (strict mode)两种类型可选。具体的细节没去研究,就不多说了。 */ int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); if (typeDiffWeight < minTypeDiffWeight) { constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } /* * 如果两个构造方法与参数值类型列表之间的差异量一致,那么这两个方法都可以作为 * 候选项,这个时候就出现歧义了,这里先把有歧义的构造方法放入 * ambiguousConstructors 集合中 */ else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { ambiguousConstructors = new LinkedHashSet >(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); } } // 若上面未能筛选出合适的构造方法,这里将抛出 BeanCreationException 异常 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)"); } /* * 如果 constructorToUse != null,且 ambiguousConstructors 也不为空,表明解析 * 出了多个的合适的构造方法,此时就出现歧义了。Spring 不会擅自决定使用哪个构造方法, * 所以抛出异常。 */ 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); } if (explicitArgs == null) { /* * 缓存相关信息,比如: * 1. 已解析出的构造方法对象 resolvedConstructorOrFactoryMethod * 2. 构造方法参数列表是否已解析标志 constructorArgumentsResolved * 3. 参数值列表 resolvedConstructorArguments 或 preparedConstructorArguments * * 这些信息可用在其他地方,用于进行快捷判断 */ argsHolderToUse.storeCache(mbd, constructorToUse); } } try { Object beanInstance; if (System.getSecurityManager() != null) { final Constructor> ctorToUse = constructorToUse; final Object[] argumentsToUse = argsToUse; beanInstance = AccessController.doPrivileged(new PrivilegedAction
上面的方法逻辑比较复杂,做了不少事情,该方法的核心逻辑是根据参数值类型筛选合适的构造方法。解析出合适的构造方法后,剩下的工作就是构建 bean 对象了,这个工作交给了实例化策略去做。下面罗列一下这个方法的工作流程吧:
创建 BeanWrapperImpl 对象
解析构造方法参数,并算出 minNrOfArgs
获取构造方法列表,并排序
遍历排序好的构造方法列表,筛选合适的构造方法
获取构造方法参数列表中每个参数的名称
再次解析参数,此次解析会将
计算构造方法参数列表与参数值列表之间的类型差异量,以筛选出更为合适的构造方法
缓存已筛选出的构造方法以及参数值列表,若再次创建 bean 实例时,可直接使用,无需再次进行筛选
使用初始化策略创建 bean 对象
将 bean 对象放入 BeanWrapperImpl 对象中,并返回该对象
由上面的流程可以看得出,通过构造方法自动注入的方式构造 bean 对象的过程还是很复杂的。为了看懂这个流程,我进行了多次调试,算是勉强弄懂大致逻辑。由于时间有限,我并未能详细分析 autowireConstructor 方法及其所调用的一些方法,比如 resolveConstructorArguments、 autowireConstructor 等。关于这些方法,这里只写了个大概,有兴趣的朋友自己去探索吧。
2.3 通过默认构造方法创建 bean 对象看完了上面冗长的逻辑,本节来看点轻松的吧 - 通过默认构造方法创建 bean 对象。如下:
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { try { Object beanInstance; final BeanFactory parent = this; // if 条件分支里的一大坨是 Java 安全相关的代码,可以忽略,直接看 else 分支 if (System.getSecurityManager() != null) { beanInstance = AccessController.doPrivileged(new PrivilegedAction
上面就是通过默认构造方法创建 bean 对象的过程,比较简单,就不多说了。最后我们再来看看简单看看通过无参构造方法刚创建 bean 对象的代码(通过 CGLIB 创建 bean 对象的方式就不看了)是怎样的,如下:
public staticT instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); try { // 设置构造方法为可访问 ReflectionUtils.makeAccessible(ctor); // 通过反射创建 bean 实例,这里的 args 是一个没有元素的空数组 return ctor.newInstance(args); } catch (InstantiationException ex) { throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); } catch (IllegalAccessException ex) { throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); } catch (IllegalArgumentException ex) { throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); } catch (InvocationTargetException ex) { throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); } }
到这里,终于看到了创建 bean 对象的代码了。在经历层层调用后,我们总算是追到了调用栈的最深处。看到这里,大家可以休息一下了,本文也差不多要结束了。好了,最后再容我多啰嗦一会,往下看。
3.写在最后写到这里,我也算是松了一口气,终于快写完了。这篇文章写起来感觉挺不容易的,原因是 createBeanInstance 及其调用的方法是在太多了,而且很多方法逻辑还是比较复杂的,尤其是 autowireConstructor 中调用的一些方法。autowireConstructor 中调用的方法我基本上都看了一遍,但并非全部都弄懂了,有些方法只是知道个大概。所以,这篇文章写的我挺纠结的,生怕有些地方分析的不对。由于我后续还有很多东西要看,以至于我暂时没法抽出大量的时间去详细阅读 Spring 的源码。所以如果上面的分析有不对的地方,欢迎指正,我会虚心听之。如果这些不对的地方给你造成了困扰,实在很抱歉,抱歉。
好了,本篇文章先到这里。谢谢阅读!
参考《Spring 源码深度解析》- 郝佳
附录:Spring 源码分析文章列表 Ⅰ. IOC更新时间 | 标题 |
---|---|
2018-05-30 | Spring IOC 容器源码分析系列文章导读 |
2018-06-01 | Spring IOC 容器源码分析 - 获取单例 bean |
2018-06-04 | Spring IOC 容器源码分析 - 创建单例 bean 的过程 |
2018-06-06 | Spring IOC 容器源码分析 - 创建原始 bean 对象 |
2018-06-08 | Spring IOC 容器源码分析 - 循环依赖的解决办法 |
2018-06-11 | Spring IOC 容器源码分析 - 填充属性到 bean 原始对象 |
2018-06-11 | Spring IOC 容器源码分析 - 余下的初始化工作 |
更新时间 | 标题 |
---|---|
2018-06-17 | Spring AOP 源码分析系列文章导读 |
2018-06-20 | Spring AOP 源码分析 - 筛选合适的通知器 |
2018-06-20 | Spring AOP 源码分析 - 创建代理对象 |
2018-06-22 | Spring AOP 源码分析 - 拦截器链的执行过程 |
更新时间 | 标题 |
---|---|
2018-06-29 | Spring MVC 原理探秘 - 一个请求的旅行过程 |
2018-06-30 | Spring MVC 原理探秘 - 容器的创建过程 |
本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处
作者:coolblog.xyz
本文同步发布在我的个人博客:http://www.coolblog.xyz
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69664.html
摘要:实例化时,发现又依赖于。一些缓存的介绍在进行源码分析前,我们先来看一组缓存的定义。可是看完源码后,我们似乎仍然不知道这些源码是如何解决循环依赖问题的。 1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的。在本篇文章中,我会首先向大家介绍一下什么是循环依赖。然后,进入源码分析阶段。为了更好的说明 Spring 解决循环依赖的办法,我将会从获取 bean 的方法getB...
摘要:简介本篇文章是容器源码分析系列文章的最后一篇文章,本篇文章所分析的对象是方法,该方法用于对已完成属性填充的做最后的初始化工作。后置处理器是拓展点之一,通过实现后置处理器接口,我们就可以插手的初始化过程。 1. 简介 本篇文章是Spring IOC 容器源码分析系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bean 做最...
摘要:本文是容器源码分析系列文章的第一篇文章,将会着重介绍的一些使用方法和特性,为后续的源码分析文章做铺垫。我们可以通过这两个别名获取到这个实例,比如下面的测试代码测试结果如下本小节,我们来了解一下这个特性。 1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了...
摘要:源码分析源码一览本节,我们先来看一下填充属性的方法,即。所有的属性值是在方法中统一被注入到对象中的。检测是否存在与相关的或。这样可以在很大程度上降低源码分析的难度。若候选项是非类型,则表明已经完成了实例化,此时直接返回即可。 1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的。我在前面几篇文章中介绍过 Spring 创建 bea...
摘要:关于创建实例的过程,我将会分几篇文章进行分析。源码分析创建实例的入口在正式分析方法前,我们先来看看方法是在哪里被调用的。时,表明方法不存在,此时抛出异常。该变量用于表示是否提前暴露单例,用于解决循环依赖。 1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑。对于已实例化好的单例 bean,getBean(String) ...
阅读 3478·2021-11-17 17:01
阅读 3887·2021-11-08 13:12
阅读 2441·2021-10-08 10:04
阅读 637·2021-09-29 09:35
阅读 1369·2021-09-26 10:12
阅读 1924·2021-09-07 09:58
阅读 1924·2019-08-30 15:55
阅读 2107·2019-08-30 13:14