百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

「死磕 Spring」—– IOC 之构造函数实例化 bean

lipiwang 2024-11-15 22:03 42 浏览 0 评论

原文出自:http://cmsblogs.com

作者:作者:chenssy

createBeanInstance() 用于实例化 bean,它会根据不同情况选择不同的实例化策略来完成 bean 的初始化,主要包括:

  • Supplier 回调:obtainFromSupplier()
  • 工厂方法初始化:instantiateUsingFactoryMethod()
  • 构造函数自动注入初始化:autowireConstructor()
  • 默认构造函数注入:instantiateBean()

在上篇博客(【死磕 Spring】----- IOC 之 Factory 实例化 bean) 中分析了 Supplier 回调和工厂方法初始化,这篇分析两个构造函数注入。

autowireConstructor()

这个初始化方法我们可以简单理解为是带有参数的初始化 bean 。代码段如下:

 public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
 @Nullable Constructor<?>[] chosenCtors, @Nullable final Object[] explicitArgs) {
 // 封装 BeanWrapperImpl 并完成初始化
 BeanWrapperImpl bw = new BeanWrapperImpl();
 this.beanFactory.initBeanWrapper(bw);
 // 构造函数
 Constructor<?> constructorToUse = null;
 // 构造参数
 ArgumentsHolder argsHolderToUse = null;
 Object[] argsToUse = null;
 /*
 * 确定构造参数
 */
 // 如果 getBean() 已经传递,则直接使用
 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) {
 argsToResolve = mbd.preparedConstructorArguments;
 }
 }
 }
 // 缓存中存在,则解析存储在 BeanDefinition 中的参数
 // 如给定方法的构造函数 A(int ,int ),则通过此方法后就会把配置文件中的("1","1")转换为 (1,1)
 // 缓存中的值可能是原始值也有可能是最终值
 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 {
 // 从 BeanDefinition 中获取构造参数,也就是从配置文件中提取构造参数
 ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
 resolvedValues = new ConstructorArgumentValues();
 // 解析构造函数的参数
 // 将该 bean 的构造函数参数解析为 resolvedValues 对象,其中会涉及到其他 bean
 minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
 }
 /*
 * 获取指定的构造函数
 */
 // 根据前面的判断,chosenCtors 应该为 null
 Constructor<?>[] candidates = chosenCtors;
 if (candidates == null) {
 // 获取 bean 的 class
 Class<?> beanClass = mbd.getBeanClass();
 try {
 // 根据 class 获取所有的构造函数
 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);
 }
 }
 // 对构造函数进行排序处理
 // public 构造函数优先参数数量降序,非public 构造函数参数数量降序
 AutowireUtils.sortConstructors(candidates);
 // 最小参数类型权重
 int minTypeDiffWeight = Integer.MAX_VALUE;
 Set<Constructor<?>> ambiguousConstructors = null;
 LinkedList<UnsatisfiedDependencyException> causes = null;
 // 迭代所有构造函数
 for (Constructor<?> candidate : candidates) {
 // 获取该构造函数的参数类型
 Class<?>[] paramTypes = candidate.getParameterTypes();
 // 如果已经找到选用的构造函数或者需要的参数个数小于当前的构造函数参数个数,则终止
 // 因为已经按照参数个数降序排列了
 if (constructorToUse != null && argsToUse.length > paramTypes.length) {
 break;
 }
 // 参数个数不等,继续
 if (paramTypes.length < minNrOfArgs) {
 continue;
 }
 // 参数持有者
 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);
 }
 catch (UnsatisfiedDependencyException ex) {
 if (this.beanFactory.logger.isTraceEnabled()) {
 this.beanFactory.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 {
 // 构造函数没有参数
 if (paramTypes.length != explicitArgs.length) {
 continue;
 }
 argsHolder = new ArgumentsHolder(explicitArgs);
 }
 // isLenientConstructorResolution 判断解析构造函数的时候是否以宽松模式还是严格模式
 // 严格模式:解析构造函数时,必须所有的都需要匹配,否则抛出异常
 // 宽松模式:使用具有"最接近的模式"进行匹配
 // typeDiffWeight:类型差异权重
 int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
 argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
 // 如果它代表着当前最接近的匹配则选择其作为构造函数
 if (typeDiffWeight < minTypeDiffWeight) {
 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);
 }
 }
 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);
 }
 // 将构造函数、构造参数保存到缓存中
 if (explicitArgs == null) {
 argsHolderToUse.storeCache(mbd, constructorToUse);
 }
 }
 try {
 // 获取创建 bean 的策略
 final InstantiationStrategy strategy = beanFactory.getInstantiationStrategy();
 Object beanInstance;
 if (System.getSecurityManager() != null) {
 final Constructor<?> ctorToUse = constructorToUse;
 final Object[] argumentsToUse = argsToUse;
 // 实例化 bean
 beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
 strategy.instantiate(mbd, beanName, beanFactory, ctorToUse, argumentsToUse),
 beanFactory.getAccessControlContext());
 }
 else {
 // 实例化bean
 beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
 }
 // 将构造的 bean 加入到 BeanWrapper 实例中
 bw.setBeanInstance(beanInstance);
 return bw;
 }
 catch (Throwable ex) {
 throw new BeanCreationException(mbd.getResourceDescription(), beanName,
 "Bean instantiation via constructor failed", ex);
 }
 }

代码与 instantiateUsingFactoryMethod() 一样,又长又难懂,但是如果理解了 instantiateUsingFactoryMethod() 初始化 bean 的过程,那么 autowireConstructor() 也不存在什么难的地方了,一句话概括:首先确定构造函数参数、构造函数,然后调用相应的初始化策略进行 bean 的初始化。关于如何确定构造函数、构造参数,该部分逻辑和 instantiateUsingFactoryMethod() 基本一致,所以这里不再重复阐述了,具体过程请移步【死磕 Spring】----- IOC 之 Factory 实例化 bean,这里我们重点分析初始化策略。 对于初始化策略,首先是获取实例化 bean 的策略,如下:

final InstantiationStrategy strategy = beanFactory.getInstantiationStrategy();

然后是调用其 instantiate()方法,该方法在 SimpleInstantiationStrategy 中实现,如下:

如果该 bean 没有配置 lookup-method、replaced-method 标签或者 @Lookup 注解,则直接通过反射的方式实例化 bean 即可,方便快捷,但是如果存在需要覆盖的方法或者动态替换的方法则需要使用 CGLIB 进行动态代理,因为可以在创建代理的同时将动态方法织入类中。反射 调用工具类 BeanUtils 的 instantiateClass() 方法完成反射工作:

 public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
 Assert.notNull(ctor, "Constructor must not be null");
 try {
 ReflectionUtils.makeAccessible(ctor);
 return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
 KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
 }
 // 省略一些 catch 
 }
CGLIB protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
 throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
 }

方法默认是没有实现的,具体过程由其子类 CglibSubclassingInstantiationStrategy 实现:

 protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
 return instantiateWithMethodInjection(bd, beanName, owner, null);
 }
 protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
 @Nullable Constructor<?> ctor, @Nullable Object... args) {
 // 通过CGLIB生成一个子类对象
 return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
 }

创建一个 CglibSubclassCreator 对象,调用其 instantiate() 方法生成其子类对象:

到这类 CGLIB 的方式分析完毕了,当然这里还没有具体分析 CGLIB 生成子类的详细过程,具体的过程等后续分析 AOP 的时候再详细地介绍。

instantiateBean()

这个方法相比于 instantiateUsingFactoryMethod() 、 autowireConstructor() 方法实在是太简单了,因为它没有参数,所以不需要确认经过复杂的过来来确定构造器、构造参数,所以这里就不过多阐述了。 对于 createBeanInstance() 而言,他就是选择合适实例化策略来为 bean 创建实例对象,具体的策略有:Supplier 回调方式、工厂方法初始化、构造函数自动注入初始化、默认构造函数注入。其中工厂方法初始化和构造函数自动注入初始化两种方式最为复杂,主要是因为构造函数和构造参数的不确定性,Spring 需要花大量的精力来确定构造函数和构造参数,如果确定了则好办,直接选择实例化策略即可。当然在实例化的时候会根据是否有需要覆盖或者动态替换掉的方法,因为存在覆盖或者织入的话需要创建动态代理将方法织入,这个时候就只能选择 CGLIB 的方式来实例化,否则直接利用反射的方式即可,方便快捷。 到这里 createBeanInstance() 的过程就已经分析完毕了,下篇介绍 doCreateBean() 方法中的第二个过程:属性填充。

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: