从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被赋值为了s2.如果配置文件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"/>@Testpublic 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。
进行汇总 汇总成了 RootBeanDefinitionSpring这样设计,是为了将共有的东西进行抽取到父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@ResourcespopulateBean()方法中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.rhkb.cn/news/84170.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux操作系统】makefile入门:一个规则-两个函数-三个变量

在Linux中&#xff0c;makefile是一种非常重要的工具&#xff0c;用于自动化构建和管理项目。它可以帮助开发人员轻松地编译和链接程序&#xff0c;同时还可以处理依赖关系和增量构建等问题。在makefile中&#xff0c;我们将重点介绍makefile中的一个规则&#xff0c;两个函数和…

湘大 XTU OJ 1214 A+B IV 题解:数位移动的本质+布尔变量标记+朴素模拟

一、链接 AB IV 二、题目 题目描述 小明喜欢做ab的算术&#xff0c;但是他经常忘记把末位对齐&#xff0c;再进行加&#xff0c;所以&#xff0c;经常会算错。 比如1213&#xff0c;他把12左移了1位&#xff0c;结果变成了133。 小明已经算了一些等式&#xff0c;请计算一下…

harbor搭建

回到目录 Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务 通俗的讲&#xff0c;harbor是一个私人镜像存储服务器 1 下载安装 进入官网&#xff0c;下载一个离线安装包,harbor官网下载 这…

Vc - Qt - QToolButton

QToolButton 是 Qt 框架中的一个类&#xff0c;是 QPushButton 的子类。它可以显示一个可单击的按钮&#xff0c;并且可以与弹出菜单、图标和文本等进行关联。 QToolButton的一些常见特性和用法包括&#xff1a; 设置文本&#xff1a;使用 setText() 函数设置按钮上的文本。设置…

AES加密(1):AES基础知识和计算过程

从产品代码的安全角度考虑&#xff0c;我们需要对代码、数据进行加密。加密的算法有很多种&#xff0c;基于速度考虑&#xff0c;我们一般使用对称加密算法&#xff0c;其中有一种常见的对称加密算法&#xff1a;AES(Advanced Encryption Standard)。在一些高端的MCU&#xff0…

[虚幻引擎] UE DTBase64 插件说明 使用蓝图对字符串或文件进行Base64加密解密

本插件可以在虚幻引擎中使用蓝图对字符串&#xff0c;字节数组&#xff0c;文件进行Base64的加密和解密。 目录 1. 节点说明 String To Base64 Base64 To String Binary To Base64 Base64 To Binary File To Base64 Base64 To File 2. 案例演示 3. 插件下载 1. 节点说…

普罗米修斯之一实现图形化监控

普罗米修斯之一实现图形化监控 1&#xff1a;prometheus1. 下载&#xff1a;2. 安装&#xff1a;3. 启动&#xff1a;1&#xff1a;启动方式之一加入systemctl2&#xff1a;启动方式之二---直接启动3&#xff1a;启动方式之三----后台运行 4&#xff1a;默认配置文件prometheus…

Idea使用Docker插件实现maven打包自动构建镜像

Docker 开启TCP 服务 vi /lib/systemd/system/docker.service改写以下内容 ExecStart/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock重启服务 #重新加载配置文件 systemctl daemon-reload #重启服务 systemctl restart docker.service此时docker已…

考研408 | 【计算机网络】物理层

导图&#xff1a; 一、通信基础 基本概念&#xff1a; 物理层接口特性&#xff1a;物理层解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。 物理层主要任务&#xff1a;确定与传输媒体接口有关的一些特性 典型的数据通信模型 数据通…

Springboot中拦截GET请求获取请求参数验证合法性

目录 目的 核心方法 完整代码 创建拦截器 注册拦截器 测试效果 目的 在Springboot中创建拦截器拦截所有GET类型请求&#xff0c;获取请求参数验证内容合法性防止SQL注入&#xff08;该方法仅适用拦截GET类型请求&#xff0c;POST类型请求参数是在body中&#xff0c;所以下面…

WhatsApp 实时聊天小插件:快速触达客户的秘密

当您进入商店时&#xff0c;您希望销售人员会向您打招呼&#xff0c;或者至少在您需要时可以找到人提供帮助。对于电子商务商店&#xff0c;客户的期望不会降低。但谁应该担任 24-7的商店经理&#xff1f;实时聊天可以成为您的电子商务商店经理。 什么是 WhatsApp 实时聊天小插…

MySQL 索引 详解

一、索引概述 索引是帮助 MySQL 高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c;这样就可以在这些数据结构上…

XML(eXtensible Markup Language)

目录 为什么需要XML? 一 XML语法 1.文档声明 2.元素 语法: 3.属性 4.注释 5.CDATA节 二 树结构 三 转义字符 四 DOM4J 1.XML解析技术 2.dom4j介绍 3.dom4j基本使用 XML 指可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;。 XML 被设计用来传…

Redux中reducer 中为什么每次都要返回新的state!!!

Redux中reducer 中为什么每次都要返回新的state&#xff01;&#xff01;&#xff01; 最近在学习react相关的知识&#xff0c;学习redux的时候遇到看到一个面试题&#xff1a; 如果Redux没返回新的数据会怎样&#xff1f; 这就是要去纠结为什么编写reducer得时候为什么不允许直…

【数学】CF1796 C

Problem - 1796C - Codeforces 题意&#xff1a; 思路&#xff1a; 模拟一下样例可以发现一些规律 Code&#xff1a; #include <bits/stdc.h>#define int long longusing i64 long long;constexpr int N 1e6 10; constexpr int mod 998244353;void solve() {int l…

自定义注解(Annontation)

目录 1.注解定义 2.元注解定义 3. 自定义注解&#xff08;自定义的注解名称相同的会覆盖原注解&#xff09; 4.Annotation架构&#xff08;元注解参数介绍&#xff09; 1.注解定义 注解是用来将任何的信息或元数据&#xff08;metadata&#xff09;与程序元素&#xff08;类…

激荡十三年,消费金融进入“体验争夺战”的下半场

消费金融行业又开始涌动着变局。 先是一些老玩家悬着的心&#xff0c;终于落地。过去两年&#xff0c;消费金融是蚂蚁集团整改的关键板块。前不久&#xff0c;蚂蚁集团被监管部门开出71.23亿元的“罚单”&#xff0c;市场普遍认为这是利空出尽的信号。 与此同时&#xff0c;竞…

【RTT驱动框架分析06】-pwn驱动框架分析+pwm驱动实现

pwm pwm应用程序开发 访问 PWM 设备API 应用程序通过 RT-Thread 提供的 PWM 设备管理接口来访问 PWM 设备硬件&#xff0c;相关接口如下所示&#xff1a; 函数描述rt_device_find()根据 PWM 设备名称查找设备获取设备句柄rt_pwm_set()设置 PWM 周期和脉冲宽度rt_pwm_enable…

PyTorch 微调终极指南:第 2 部分 — 提高模型准确性

一、说明 如今&#xff0c;在训练深度学习模型时&#xff0c;通过在自己的数据上微调预训练模型来迁移学习已成为首选方法。通过微调这些模型&#xff0c;我们可以利用他们的专业知识并使其适应我们的特定任务&#xff0c;从而节省宝贵的时间和计算资源。本文分为四个部分&…

使用 `nmcli` 在 CentOS 8 上添加永久路由

CentOS 8 使用 NetworkManager 作为默认的网络管理工具&#xff0c;因此我们可以使用 nmcli 工具来实现相同的目标。使用 nmcli 可以更加直观地管理路由&#xff0c;并且更符合 CentOS 8 的默认网络管理方式。 以下是使用 nmcli 在 CentOS 8 上添加永久路由的步骤&#xff1a;…