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

从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中进行的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql基础之触发器的简单使用

1.建立学生信息表 -- 触发器 -- 建立学生信息表 create table s1(id int unsigned auto_increment,name varchar(30),score tinyint unsigned,dept varchar(50),primary key(id) );2.建立学生补考信息表 -- 建立学生补考信息表 create table s2 like s1;3.建立触发器&#xf…

Grafana技术文档-概念-《十分钟扫盲》

Grafana官网链接 Grafana: The open observability platform | Grafana Labs 基本概念 Grafana是一个开源的度量分析和可视化套件&#xff0c;常用于对大量数据进行实时分析和可视化。以下是Grafana的基本概念&#xff1a; 数据源&#xff08;Data Source&#xff09;&#…

【大数据】Flink 详解(一):基础篇

Flink 详解&#xff08;一&#xff09;&#xff1a;基础篇 1、什么是 Flink &#xff1f; Flink 是一个以 流 为核心的高可用、高性能的分布式计算引擎。具备 流批一体&#xff0c;高吞吐、低延迟&#xff0c;容错能力&#xff0c;大规模复杂计算等特点&#xff0c;在数据流上提…

模板的进阶

目录 1.非类型模板参数 2.模板特化 2.1概念 2.2函数模板特化 2.3类模板特化 2.3.1全特化 2.3.2偏特化 3.模板分离编译 3.1什么是分离编译 3.2 模板的分离编译 3.3解决方法 4. 模板总结 1.非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a…

Python(七十五--总结)列表、字典、元组、集合总结

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

关于Object 0 = new Object() 的追魂九连问

文章目录 对象的创建过程对象的组成解析普通对象**结果分析&#xff1a;**给对象添加属性注意事项 补充jvm压缩指针栗子&#xff1a; 对象头包含什么对象怎么定位&#xff1f;**句柄方式和直接引用的优缺点&#xff1a;** 对象怎么分配&#xff1f;为什么hotspot不使用c对象来代…

QT的信号槽的四种写法和五种链接方式

目录 四种信号槽写法&#xff1a; 五种连接方式&#xff1a; 实例&#xff1a; 常见错误及改正&#xff1a; 错误1: 未连接信号与槽 错误2: 信号和槽参数不匹配 错误3: 未使用Q_OBJECT宏 错误4: 跨线程连接未处理 在Qt中&#xff0c;信号&#xff08;Signal&#xff09…

Stephen Wolfram:让 ChatGPT 真正起作用的是什么?

What Really Lets ChatGPT Work? 让 ChatGPT 真正起作用的是什么&#xff1f; Human language—and the processes of thinking involved in generating it—have always seemed to represent a kind of pinnacle of complexity. And indeed it’s seemed somewhat remarkabl…

go-admin 使用开发

在项目中使用redis 作为数据缓存&#xff1a;首先引入该包 “github.com/go-redis/redis/v8” client : redis.NewClient(&redis.Options{Addr: config.QueueConfig.Redis.Addr, // Redis 服务器地址Password: config.QueueConfig.Redis.Password, // Redis 密码&…

Vue自定义指令使用

本篇文章讲述使用Vue自定义指令&#xff0c;并在项目中完成相应功能。 在平常Vue脚手架项目中&#xff0c;使用到 自定义指令较少&#xff0c;一般都是使用的自带指令&#xff0c;比如 v-show 、v-if 、 v-for 、 v-bind 之类的。这些已经能够满足大多数项目使用。更多的可能也…

springboot+mybatis实现简单的增、删、查、改

这篇文章主要针对java初学者&#xff0c;详细介绍怎么创建一个基本的springboot项目来对数据库进行crud操作。 目录 第一步&#xff1a;准备数据库 第二步&#xff1a;创建springboot项目 方法1&#xff1a;通过spring官网的spring initilizer创建springboot项目 方法2&am…

UG NX二次开发(C#)-CAM自定义铣加工的出口环境

文章目录 1、前言2、自定义铣削加工操作3、出错原因4、解决方案4.1 MILL_USER的用户参数4.2 采用自定义铣削的方式生成自定义的dll4.2 配置加工的出口环境4.3 调用dll5、结论1、前言 作为一款大型的CAD/CAM软件, UG NX为我们提供了丰富的加工模板,通过加工模板能直接用于生成…

day7 8-牛客67道剑指offer-JZ74、57、58、73、61、62、64、65、把字符串转换成整数、数组中重复的数字

文章目录 1. JZ74 和为S的连续正数序列暴力解法滑动窗口&#xff08;双指针&#xff09; 2. JZ57 和为S的两个数字3. JZ58 左旋转字符串4. JZ73 翻转单词序列5. JZ61 扑克牌顺子6. JZ62 孩子们的游戏(圆圈中最后剩下的数)迭代 模拟递归 约瑟夫环问题 找规律 7. JZ64 求123...n8…

0基础学C#笔记08:插入排序法

文章目录 前言一、过程简单描述&#xff1a;二、代码总结 前言 我们在玩打牌的时候&#xff0c;你是怎么整理那些牌的呢&#xff1f;一种简单的方法就是一张一张的来&#xff0c;将每一张牌插入到其他已经有序的牌中的适当位置。当我们给无序数组做排序的时候&#xff0c;为了…

SpringBoot 该如何预防 XSS 攻击

XSS 漏洞到底是什么&#xff0c;说实话我讲不太清楚。但是可以通过遇到的现象了解一下。在前端Form表单的输入框中&#xff0c;用户没有正常输入&#xff0c;而是输入了一段代码&#xff1a;</input><img src1 onerroralert1> 这个正常保存没有问题。问题出在了列表…

竞赛项目 深度学习疲劳驾驶检测 opencv python

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

HCIP-linux和kvm(ks配置文件自动化安装及console连虚拟机有问题)

1、linux linux安装教程参考&#xff0c;https://blog.51cto.com/cloudcs/5245337 yum源配置 本地yum源配置&#xff1a; 8版本配置&#xff1a;将光盘iso挂载到某个目录&#xff0c;/dev/cdrom是/dev/sr0软链接&#xff0c;# mount /dev/cdrom /mnt&#xff0c;# ls /mnt Ap…

.NET6使用SqlSugar操作数据库

1.//首先引入SqlSugarCore包 2.//新建SqlsugarSetup类 public static class SqlsugarSetup{public static void AddSqlsugarSetup(this IServiceCollection services, IConfiguration configuration,string dbName "ConnectString"){SqlSugarScope sqlSugar new Sq…

手动创建一个DOCKER镜像

1. 我们先使用C语言写一个hello-world程序 vim hello.c # include <stdio.h>int main() {print("hello docker\n"); } 2. 将hello.c文件编译成二进制文件, 需要安装工具 yum install gcc yum install glibc-static 开始编译 gcc -static hello.c -o hello 编译…

Mybatis Plus条件构造器LambdaQueryWrapper

官网地址 Mybatis Plus条件构造器LambdaQueryWrapper 目前数据库数据情况&#xff0c;User表 iduser_namebirthdaysexaddress1张12023-08-10男123163.com2李12023-08-10女222163.com3张22023-08-10女999163.com4张32023-08-10男9994qq.com ## 简单介绍 如何使用各种场景 方法…