从Spring源码看创建对象的过程

news2024/11/24 20:27:26

从Spring源码看创建对象的过程

Spring对于程序员set注入的属性叫做属性的填充、对于set注入之后的处理(包括BeanPostProcessor的处理、初始化方法的处理)叫做初始化。

研读AbstractBeanFactory类中的doGetBean()方法

doGetBean()方法首先完成的工作是获取对象(针对于 scope=singleton 这种形式的对象,Spring把曾经创建获得对象进行存储。后续先获取对象),如果获取不到,才会创建对象。

源码图如下:

粗略看创建对象的大体流程

从代码中,我们可以看到一行注释:

Create bean instance.

createBean

这是通过scope进行讨论,以单例情况为例:

  1. 调用AbstractAutowireCapableBeanFactory类中的

    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法
    

    createBean核心源码

  2. createBean方法又会调用本类中的doCreateBean方法;

  3. doCreateBean方法中createBeanInstance就是使用反射进行对象的创建;

    image-20230807200114398

  4. populateBean来完成属性填充

    属性的填充

    包括set注入
    和自动注入
        <bean autowired="byName|byType"
        <beans default-autowried 
        @Autowired
    
  5. initializeBean方法进行初始化操作。

    初始化操作

详细阅读doGetBean方法

首先来看一下两个参数

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException
name就是bean的id值;
requiredType指创建bean的类型;

代码详解:

final String beanName = transformedBeanName(name);
大多数情况下name和beanName是一样的,就是bean的id值;
但是还有两种情况,需要转换;
    1.实现FactoryBean接口类bean的id;
    比如 beanFactory.getBean("&s") 会把&去掉,剩下s;此时name为&s,beanName被赋值为了s
    2.如果配置文件bean为<bean id="user" name="u"  class="xxxx" />
    beanFactory.getBean("u"),通过别名u来获取bean,会转换成id。
Object bean;
代表最终创建好的对象
Object sharedInstance = getSingleton(beanName);
会从DefaultSingletonBeanRegistry 类中的 singletonObjects、earlySingletonObjects 、singletonFactories获取(从这三个中获取,是为了解决循环引用的问题);
	
1.第一次获取,会获取为null;
2.后续从Spring容器获取,可以获取到:
注意:Spring创建对象对象会有2种状态
    1 完全状态: 对象创建完成 属性填充完成 初始化完成 (如果需要代理 AOP,也已经完成)  
    2 正在创建中: 仅仅有一个简单对象

doGetBean中调用的DefaultSingletonBeanRegistry中getSingleton方法定义如下:

getSingleton

if(sharedInstance != null && args == null) {
    if(logger.isTraceEnabled()) {
        if(isSingletonCurrentlyInCreation(beanName)) {——————————判断获取到的bean是否在创建中
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
        } else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
        }
    }————————这些代码是帮Spring开发人员作代码跟踪的
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);分析

sharedInstance是从getSingleton()方法中获取到的对象,它与返回的bean有什么区别,就是这行代码的主要功能。

applicationContext.xml配置文件中
如果bean的配置为:
<bean id="u" class="xxxx.User"/> 
那么获取到的sharedInstance 就等同于 bean;

如果bean的配置为:
<bean id="fb" class="xxxxx.XXXFactoryBean"/> 
此时获取到的sharedInstance是XXXFactoryBean类的对象,我们想获取的是XXXFactoryBean的getObject()里的对象,此时getObjectForBeanInstance(sharedInstance, name, beanName, null)返回返回 FactoryBean#getObject();

所以:
getObjectForBeanInstance(sharedInstance, name, beanName, null);
如果是简单对象 bean就是sharedInstance 
如果是FactoryBean对象 bean是 FactoryBean#getObject()返回的对象
if(isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}
用来校验:如果已经在创建这个bean了,抛出异常
BeanFactory parentBeanFactory = getParentBeanFactory();
if(parentBeanFactory != null && !containsBeanDefinition(beanName)) {
    // Not found -> check parent.
    String nameToLookup = originalBeanName(name);
    if(parentBeanFactory instanceof AbstractBeanFactory) {
        return((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
    } else if(args != null) {
        // Delegation to parent with explicit args.
        return(T) parentBeanFactory.getBean(nameToLookup, args);
    } else if(requiredType != null) {
        // No args -> delegate to standard getBean method.
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    } else {
        return(T) parentBeanFactory.getBean(nameToLookup);
    }
}
======doGetBean源码结束
用来解决Spring的父子容器问题:
    父子容器代码演示
 
    //applicationContext-parents.xml中有配置bean,
    <bean id="p" class="com.sjdwz.Product"/>
    applicationContext.xml中有配置bean
    <bean id="u"  class="com.sjdwz.User"/>
    @Test
    public void test3() {
        DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(parent);
        xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("applicationContext-parents.xml"));
        DefaultListableBeanFactory child = new DefaultListableBeanFactory(parent);//把父容器传进来,作联系
        XmlBeanDefinitionReader xmlBeanDefinitionReader1 = new XmlBeanDefinitionReader(child);
        xmlBeanDefinitionReader1.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
        //一旦使用父子容器 最终父子容器的配置信息 融合
        // 如果遇到同名的配置内容 使用子容器中的内容
        User u = (User) child.getBean("u");
        System.out.println("u = " + u);
        Product p = child.getBean("p", Product.class);
        System.out.println("p = " + p);
    }
    注意:如果子容器和父容器有相同类的注入,并且id也相同,会用子容器里的配置。
	如果有父容器有bean的配置信息,在子容器中没有,那么实例化父容器对应的bean(递归进行)
if(!typeCheckOnly) {
    markBeanAsCreated(beanName);
}

    typeCheckOnly是doGetBean()方法的参数,在调用getBean时,设置的值为false;
    typeCheckOnly 标志 false 【默认】
    typeCheckOnly=true代表 spring只是对获取类型进行判断而并不是创建对象
    beanFactory.getBean("u",User.class)
    typeCheckOnly = true:spring是不会创建User对象 判断当前工厂获得或者创建的对象类型是不是User类型
    typeCheckOnly=false:spring会创建对象 或者 获得这个对象 返回给调用者


	markBeanAsCreated(beanName);标志这个bean时需要常见对象,而不是进行类型检查;
	他需要做两件事:
		1. 标记这个bean被创建
		2. clearMergedBeanDefinition(beanName);

markBeanAsCreated(beanName)源码:

markBeanAsCreated方法

要想理解markBeanAsCreated中的clearMergedBeanDefinition()概念,要继续往后看doGetBean()源码。
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

Spring中bean是有继承关系的。
<bean id="p" abstract=true"/>————如果一个bean abstract被设置为true,那它就是专门被用来继承的抽象bean,作为父bean
<bean id="u" class="xxx.xxx" parent="p"/>————parent="p" 指定其继承于哪个bean。
进行汇总 汇总成了 RootBeanDefinition

Spring这样设计,是为了将共有的东西进行抽取到父bean中。
mergedBeanDefinition是指父子bean的定义整合到一起——————汇总成RootBeanDefinition。


这样就可以回答上面的问题了:markBeanAsCreated()中要标记它被创建完成,就意味着它合并过了,所以要进行clearMergedBeanDefinition()操作。
checkMergedBeanDefinition(mbd, beanName, args);
防止工厂中bean都是抽象bean。

checkMergedBeanDefinition方法源码

String[] dependsOn = mbd.getDependsOn();
if(dependsOn != null) {
    for(String dep: dependsOn) {
        if(isDependent(beanName, dep)) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
        }
        registerDependentBean(dep, beanName);
        try {
            getBean(dep);
        } catch(NoSuchBeanDefinitionException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
        }
    }
}

这是为了处理depends-on的(现在已经很少在开发中使用了)
<bean  depends-on=" "/> 

下面就是根据scope进行区分,来创建了:

我们开发中经常使用的是Singleton的scope,所以重点分析它。

if(mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () - > {
        try {
            return createBean(beanName, mbd, args);
        } catch(BeansException ex) {
            // Explicitly remove instance from singleton cache: It might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // Also remove any beans that received a temporary reference to the bean.
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这是要给lambda表达式:

首先调用getSingleton()方法
getSingleton()方法内部会调用createBean(beanName, mbd, args)方法(这是核心)

getSingleton()方法具体分析

getSingleton方法分析

beforeSingletonCreation(beanName)方法分析

beforeSingletonCreation方法

用来做校验,需要满足如下两个条件:
1.这个bean没有被排除
	备注:排除的概念,比如@ComponentScan(excludeFilters=)
		比如<context:component-scan base-package="com.sjdwz">
				<context:exclude-filter type="" expression="">
			</context:component-scan>
		用来排除某些bean
2.这个bean此时正在创建中
	如果在bean是被创建中的状态,就没必要再创建了,直接在缓存中获取就可以了,这是为了解决循环依赖问题的。

createBean()方法分析

createBean源码截图1

createBean源码截图2

研读doCreateBean()中创建对象的方法

doCreateBean源码截图1

为什么对返回的是instanceWrapper(bean的包装)呢?

BeanWrapper实现类

为什么要封装属性呢?便于进行类型转换;

id是Integer类型
比如标签中设置 <property name="id" value="1">
"1"会通过类型转换器转为Integer类型
或者用@PropertySource()中@Value()注解读取propery文件中,也要类型转换器来解决。

类型转换器可以分为以下两种:
1.内置类型转换器,比如:
String转Integer ,String转List,String转数组类型;
2.自定义类型转换器
比如可以自己开发一个String转Date。

类型转换器开发:
1. 使用PropertyEditorSupport,然后注册到 CustomeEditorConfigure 中;这是很早之前流行的做法,现在很少使用了;
2. 实现Converter接口,然后这个bean的名字,必须叫convertionService
createBeanInstance()就是用反射来创建对象了。

doGetBean()方法中对属性进行填充

doGetBean()中populateBean(beanName, mbd, instanceWrapper);是对属性进行填充的。

注入分为,set注入,自动注入(bean或beans标签中autowire属性),构造注入
构造注入是使用构造方法来对属性进行填充的,这应该是由createBeanInstance()方法来调用的,所以不会在中populateBean()体现。
populateBean会进行set注入和自动注入。

set注入分为:
    <bean id="" class="">
    JDK类型  <property name="" value=""/>
    自建类型 <property name="" ref=""/>
    
    
	@Value——JDK类型注入
    @Autowired——自建类型注入    
    @Inject
    @Resources
    
populateBean()方法中hasInstAwareBpps变量来判断是否是注解类型注入;——————AutowiredAnnotationBeanPostProcessor来处理注解的注入

非注解的属性填充是通过applyPropertyValues(beanName, mbd, bw, pvs);完成的
    
在注入时还会进行类型转换
      如果配置了自定义类型转换器的时候会调用自定义的,否则调用内置类型转换器
      
注意:不管是<bean >标签进行属性填充、还是注解进行属性填充,当这个类型是自定义类型时,都会继续走BeanFactory#getBean()方法

applyPropertyValues()方法说明:

applyPropertyValues方法处理标签式注入

doGetBean()方法中初始化的操作

doGetBean()中exposedObject = initializeBean(beanName, exposedObject, mbd);是进行初始化操作的。

初始化操作的流程

问题:属性填充中的BeanPostProcessor和初始化中的BeanPostProcessor有什么区别呢?

属性填充中的BeanPostProcessor,一般情况下指的是Spring自己提供的BeanPostProcessor;

初始化中的BeanPostProcessor,一般情况下指的是程序员提供的BeanPostProcessor。

下面我们再来讨论下创建对象时,为其做AOP代理是如何进行的。

是在初始化中的BeanPostProcessor的after中进行的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/860455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL高级-锁+mysql的主从关系(详解02)

目录 1.锁 1.1.Mysql锁问题 1.1.1.锁概述 1.1.2.锁分类 1.1.3.Mysql 锁 1.2.MyISAM 表锁 1.2.1.如何加表锁 1.2.2.读锁案例 1.2.3.写锁案例 1.3.InnoDB 行锁 支持事务 1.3.1.行锁介绍 1.3.2.背景知识 1.3.3.InnoDB 的行锁模式 1.3.4.案例准备工作 innodb 支持事务…

虚幻引擎游戏开发过程中,游戏鼠标如何双击判定?

UE虚幻引擎对于游戏开发者来说都不陌生&#xff0c;市面上有47%主机游戏使用虚幻引擎开发游戏。作为是一款游戏的核心动力&#xff0c;它的功能十分完善&#xff0c;囊括了场景制作、灯光渲染、动作镜头、粒子特效、材质蓝图等。本文介绍了虚幻引擎游戏开发过程中游戏鼠标双击判…

计算机网络实验4:HTTP、DNS协议分析

文章目录 1. 主要教学内容2. HTTP协议3. HTTP分析实验【实验目的】【实验原理】【实验内容】【实验思考】 4. HTTP分析实验可能遇到的问题4.1 捕捉不到http报文4.2 百度是使用HTTPS协议进行传输4.3 Wireshark获得数据太多如何筛选4.4 http报文字段含义不清楚General&#xff08…

spring boot 集成 jetcache【基础篇:@Cached、@CreateCache、@CacheRefresh】

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://zhangxiaofan.blog.csdn.net/article/details/129832925 目录 前言 版本 配置通用说明 项目结构 代码 启动类 实体类 基础使用——增删改查&#xff08;Cached、CacheInv…

github上有哪些值得读源码的react项目?

前言 下面是我整理的关于值得一读源码的react项目&#xff0c;希望对你有所帮助~ 1、 calcom Star: 21.6k calcom是一个开源的计算器应用程序。它提供了基本的数学运算功能&#xff0c;例如加法、减法、乘法和除法&#xff0c;还支持 科学计算、进制转换和单位转换等高级功能…

【刻削生千变,丹青图“万相”】阿里云AI绘画创作模型 “通义万相”测评

刻削生千变&#xff0c;丹青图“万相 4月7日&#xff0c;阿里大模型“通义千问”开始邀请用户测试体验。现阶段该模型主要定向邀请企业用户进行体验测试&#xff0c;用户可通过官网申请&#xff08;tongyi.aliyun.com&#xff09;&#xff0c;符合条件的用户可参与体验。 随…

skywalking忽略调用链路中的指定异常

文章目录 一、介绍二、演示项目介绍1. 支付服务2. 订单服务 三、项目演示1. 未忽略异常2. 忽略异常修改配置使用注解 四、结论 往期内容 一、skywalking安装教程 二、skywalking全链路追踪 三、skywalking日志收集 一、介绍 在前面介绍在微服务项目中使用skywalking进行全链…

LLaMA长度外推高性价比trick:线性插值法及相关改进源码阅读及相关记录

前言 最近&#xff0c;开源了可商用的llama2&#xff0c;支持长度相比llama1的1024&#xff0c;拓展到了4096长度&#xff0c;然而&#xff0c;相比GPT-4、Claude-2等支持的长度&#xff0c;llama的长度外推显得尤为重要&#xff0c;本文记录了三种网络开源的RoPE改进方式及相…

使用appuploader怎么安装测试​

使用appuploader怎么安装测试​ 一.安装测试​ 首先我们来看安装测试这个模块&#xff0c;注意按照上面提示内容操作。 点击首页的测试设备管理 二.选择IPA​ 进入“安装测试”页面&#xff0c;选择一个&#xff08;必须是开发类型描述文件编译&#xff0c;且描述文件包含设…

f1tenth仿真设置

文章目录 一、安装依赖二、进入工作空间克隆三、编译四、运行 一、安装依赖 tf2_geometry_msgs ackermann_msgs joy map_server sudo apt-get install ros-noetic-tf2-geometry-msgs ros-noetic-ackermann-msgs ros-melodic-joy ros-noetic-map-server 二、进入工作空间克隆…

详解Linux文本三剑客

目录 一、grep 1.什么是grep? 2.如何使用&#xff1f; 3.正则 二、sed 1.认识sed? 2.如何使用&#xff1f; 三、awk&#xff08;重点&#xff09; 1.awk变量 1.1内置变量 1.2自定义变量 2.awk数组 四、经典实战案例 案例一&#xff1a;筛选IPv4地址 案例二&am…

为MySQL新增一张performance_schema表 | StoneDB 技术分享会 #4

StoneDB开源地址 https://github.com/stoneatom/stonedb 设计&#xff1a;小艾 审核&#xff1a;丁奇、李浩 编辑&#xff1a;宇亭 作者&#xff1a;王若添 中国科学技术大学-软件工程-在读硕士、StoneDB 内核研发实习生 performance_schema 简介 MySQL 启动后会自动创建四…

基于SpringBoot+LayUI的宿舍管理系统 001

项目简介 源码来源于网络&#xff0c;项目文档仅用于参考&#xff0c;请自行二次完善哦。 系统以MySQL 8.0.23为数据库&#xff0c;在Spring Boot SpringMVC MyBatis Layui框架下基于B/S架构设计开发而成。 系统中的用户分为三类&#xff0c;分别为学生、宿管、后勤。这三…

【MySQL常见面试题】

索引的基本原理 索引⽤来快速地寻找那些具有特定值的记录。如果没有索引&#xff0c;⼀般来说执⾏查询时遍历整张表。 索引的原理&#xff1a;就是把⽆序的数据变成有序的查询 把创建了索引的列的内容进⾏排序 对排序结果⽣成倒排表 在倒排表内容上拼上数据地址链 在查询的…

计算机网络:网络通信相关概念入门

目录 一、网络发展背景二、理解网络通信三、理解IP地址1.简述IP地址2.IP地址的版本3.提高地址利用率的技术 四、理解端口1.简述端口2.使用端口的原因 五、理解网络通信协议 一、网络发展背景 网络发展背景&#xff1a; 最初的计算机是单机&#xff0c;那么单机是这样传输数据的…

谁是5G应用的狮子座?

8月5日&#xff0c;广和通公布2023年上半年财报&#xff0c;2023上半年总营业收入38.65亿元&#xff0c;同比增长59.87%。在全球经济存在诸多不确定性的背景下&#xff0c;广和通精准预判市场风向&#xff0c;制定稳健发展策略&#xff0c;业绩仍保持逆势增长。其中&#xff0c…

详谈基于布局分析的表格识别方法

基于布局分析的OCR&#xff08;Optical Character Recognition&#xff09;是一种基于页面布局信息的文本识别方法。传统的OCR系统通常依赖于表格线或者特定的格式来进行文本区域检测和字符识别&#xff0c;但对于一些表格线不全或线不清晰&#xff0c;甚至没表格线&#xff0c…

协程(一)单机--》并发--》协程

目录 一 协程的概述1.1 并行与并发1.2 线程1.3 新的思路1.4 Goroutine 二 第一个入门程序 一 协程的概述 我查看了网上的一些协程的资料&#xff0c;发现每个人对协程的概念都不一样&#xff0c;但是我认可的一种说法是&#xff1a;协程就是一种轻量级的线程框架&#xff08;K…

去趋势化一个心电图信号、信号功率谱、低通IIR滤波器并平滑信号、对滤波器引起的延迟进行补偿研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【C语言进阶篇】关于指针的八个经典笔试题(图文详解)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 指针笔试题&#x1f4ad; 笔试题 1&#xff1a;✅ 代码解析⁉️ 检验结果&…