【spring源码系列-06】refresh中obtainFreshBeanFactory方法的执行流程

Spring源码系列整体栏目


内容链接地址
【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885
【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428
【三】xml配置文件启动spring时refresh的前置工作https://blog.csdn.net/zhenghuishengq/article/details/131066637
【四】注解方式启动spring时refresh的前置工作https://blog.csdn.net/zhenghuishengq/article/details/131113249
【五】refresh中prepareRefresh的执行流程https://blog.csdn.net/zhenghuishengq/article/details/131186016
【六】refresh中obtainFreshBeanFactory的执行流程https://blog.csdn.net/zhenghuishengq/article/details/131286888

refresh中obtainFreshBeanFactory方法的执行流程

  • 一,bean工厂的创建
    • 1,refreshBeanFactory()
      • 1.1,reader.register
      • 1.2,scanner.scan
    • 2,getBeanFactory

一,bean工厂的创建

前一篇了解了refresh的第一个方法prepareRefresh ,主要是初始化环境,实例化一些监听器和环境等。接下来讲解refresh中的第二个方法,obtainFreshBeanFactory()

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		//1:准备刷新上下文环境
		prepareRefresh();
		//2:获取告诉子类初始化Bean工厂,不同工厂有不同的实现
        //  并且将配置文件的属性值加载到当前工厂中
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		//3:对bean工厂进行填充属性
		prepareBeanFactory(beanFactory);
		try {
			// 第四:留个子类去实现该接口,bean工厂的后置处理器
			postProcessBeanFactory(beanFactory);
			// 调用我们的bean工厂的后置处理器. 
       	 	//1. 会在此将class扫描成beanDefinition  2.bean工厂的后置处理器调用
			invokeBeanFactoryPostProcessors(beanFactory);
			// 注册我们bean的后置处理器
			registerBeanPostProcessors(beanFactory);
			// 初始化国际化资源处理器.
			initMessageSource();
			// 创建事件多播器
			initApplicationEventMulticaster();
			// 这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.
			onRefresh();
			//把我们的事件监听器注册到多播器上
			registerListeners();
			// 实例化我们剩余的单实例bean.
			finishBeanFactoryInitialization(beanFactory);
			// 最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)
			finishRefresh();
		}
    }
}

在refresh的前置工作中,已经提前初始化了一个默认工厂DefaultListableBeanFactory,而这一步,就是初始化具体的工厂实现。接下来继续往下面的源码看 ,此时会调用一个 obtainFreshBeanFactory() 方法

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

进去这个方法中可以发现,第一步就是会进行一个刷新bean工厂的方法 refreshBeanFactory

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	/**
	 *  xml加载spring会在这里加载beanDefinition
	 *  javaconfig只是刷新了beanFactory
	 */
	refreshBeanFactory();
	//返回我们的bean工厂
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	return beanFactory;
}

1,refreshBeanFactory()

1,接下来查看这个刷新bean工厂的方法,首先会判断bean工厂是否存在,存在则销毁原先的bean工厂,然后再创建一个默认的工厂 DefaultListableBeanFactory ,创建工厂的过程主要是加载配置文件,设置属性等。

@Override
protected final void refreshBeanFactory() throws BeansException {
	//若已经存在了 就信息销毁等操作
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		//为我们的Spring应用上下文对象创建我们的beanFactory
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		//为容器设置一个序列化ID,可以通过反序列化获取bean工厂
		beanFactory.setSerializationId(getId());
		customizeBeanFactory(beanFactory);
		//加载我们的bean定义
		loadBeanDefinitions(beanFactory);
		synchronized(this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	} catch (IOException ex) {
		...
	}
}

2,这个创建的bean工厂就是我们熟悉的默认bean工厂 DefaultListableBeanFactory

//为我们的spring 上下文创建我们的内置的beanFactory
protected DefaultListableBeanFactory createBeanFactory() {
	return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

3,随后就是设置一个序列化的id,可以通过反序列的形式获取到对应的beanFactory

beanFactory.setSerializationId(getId());

4,随后就是定制化bean工厂的操作,会设置两个重要的属性,就是允许覆盖bean定义和允许循环依赖,可以重写这两个方法,修改这两个值的默认属性。

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
	if (this.allowBeanDefinitionOverriding != null) {
		beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.allowCircularReferences != null) {
		beanFactory.setAllowCircularReferences(this.allowCircularReferences);
	}
}

5,再接着就是以加载bean定义的形式将bean工厂加载,其具体的方法实现是 loadBeanDefinitions

loadBeanDefinitions(beanFactory);

在这个加载bean定义的抽象类中,可以看到存在四种不同的实现,分别就是对应着不同方式获取上下文的方式

在这里插入图片描述

以注解的方式,继续往下看,可以发现在内部存在着大量的方法的重载,而此时bean定义的信息时存储在工厂类里面的

在这里插入图片描述

在前面两步里面,主要是创建一个读取器和扫描器,获取一些系统环境和变量,资源解析器,处理一些条件注解,加载一些bean的后置处理器等

//读取器
AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
//扫描器
ClassPathBeanDefinitionScanner scan = getClassPathBeanDefinitionScanner(beanFactory);

随后再获取bean容器的构造器,如果不存在则重新注册一个单例的构造器

BeanNameGenerator beanNameGenerator = getBeanNameGenerator();

随后再获取一个数据解析器 ScopeMetadataResolver

ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();

1.1,reader.register

在加载完这些配置之后,接下来就是真正的去对这些bean定义进行注册

reader.register(ClassUtils.toClassArray(this.annotatedClasses));

真正的注册bean的方法是这个 doRegisterBean 方法

在这里插入图片描述

该方法中会存储一些@Configuration注解的类

AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);

也会对一些条件进行筛选判断,如@ConditionalOnClass,@ConditionalOnType等,以及解析一些bean的作用域,填充一些bean的属性,如懒加载等等,最后将这个bean定义进行注册,具体的注册逻辑在上一篇详细的写过

BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)

1.2,scanner.scan

在reader注册完之后,接下来对一些xml文件或者一些包进行扫描的工作

scanner.scan(StringUtils.toStringArray(this.basePackages));

其具体的扫描扫描方法如下,真正扫描配置类的工作是这个 doScan 方法

public int scan(String... basePackages) {
	//还没有扫描mapper包之前 容器中所有的bean定义个数
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	//真正的扫描我们的mapper包的mapper类
    //此处调用的是子类的doScan因为被重写了
	doScan(basePackages);
	// 注册系统中的配置类处理器
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}
	//返回扫描出mapper的bean定义的个数
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

接下来查看这个doscan方法 , 如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k665ep9Y-1687155223629)(img/1686890716541.png)]

首先会创建一个 BeanDefinitionHolder 对象用于保存bean定义,这个对象里面存储着bean定义,属性等

//创建bean定义的holder对象用于保存扫描后生成的bean定义对象
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();

随后一步就是循环这个作为参数传进来的包路径,第一步就是找到候选的 Components

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

在上面reader.register注册中,在doRegisterBean 方法中将 @Configuration 注解先注册成beanDifinition,所以@Configuration比@Component注解先加载成bean定义。接下来继续查看这个 findCandidateComponents 方法,会对包路径进行解析

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    //验证是否为空
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
	}
	else {
		return scanCandidateComponents(basePackage);
	}
}

在使用注解获取上下文启动spring时,在refresh的前置工作中,就会注册一个 includeFiltersexcludeFilters ,默认会将所有的带有 @Component,@Controller,@Service 等等注解的类加载到这个 includeFilters 的过滤器中,也可以手动将他加入到 excludeFilters 中,这样spring容器就不会加载

protected void registerDefaultFilters() {
	//加入扫描我们的@Component的
	this.includeFilters.add(new AnnotationTypeFilter(Component.class));
	....
}

接着上文,如果是包含在这个包含过滤器中,则直接将这个容器加入到包中,否者会继续扫描这些候选者们,会判断这些文件是否可读等。

看完doScan方法找到全部的候选beanDefinition之后,这些bean定义就是符合标准可以注册到bean工厂的一些bean定义,接下来就会对这些bean定义设置一些属性,随后就是关键的一步,就是将我们解析出来的组件bean定义注册到我们的IOC容器中,随后将这个bean定义返回

if (checkCandidate(beanName, candidate)) {
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
	definitionHolder =
		AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	beanDefinitions.add(definitionHolder);
	registerBeanDefinition(definitionHolder, this.registry);
}

最后又会调用具体的 doRegisterBean 方法用于真正的注册bean定义

<T> void doRegisterBean(...){}

总结来说,不管是使用读取器注册还是使用扫描器进行扫描操作,都是为了生成bean定义,并将这个bean定义加入到BeanDifinitionMap中,随后交给bean工厂去生产。

2,getBeanFactory

在刷新完bean工厂之后,随后就是get获取一个bean工厂

ConfigurableListableBeanFactory beanFactory = getBeanFactory();

获取bean工厂的方法如下,加了一个同步锁,随后将这个bean工厂对象返回

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
	synchronized(this.beanFactoryMonitor) {
		if (this.beanFactory == null) {
			throw new IllegalStateException("...");
		}
		return this.beanFactory;
	}
}

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

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

相关文章

十个实用MySQL函数

函数 0. 显示当前时间 命令&#xff1a;。 作用: 显示当前时间。 应用场景: 创建时间&#xff0c;修改时间等默认值。 例子&#xff1a; 1. 字符长度 命令&#xff1a;。 作用: 显示指定字符长度。 应用场景: 查看字符长度时。 例子&#xff1a; 2. 日期格式化 命令…

面试---简历

项目 1.1、商品管理 新增商品 同时插入商品对应的使用时间数据&#xff0c;需要操作两张表&#xff1a;product&#xff0c;product_usetime。在productService接口中定义save方法&#xff0c;该方法接受一张Dto对象&#xff0c;dto对象继承自product类&#xff0c;并将prod…

Stable Diffusion webui 基础参数学习

哈喽&#xff0c;各位小伙伴们大家好&#xff0c;最近一直再研究人工智能类的生产力&#xff0c;不得不说随着时代科技的进步让人工智能也得到了突破性的发展。而小编前段时间玩画画也是玩的不可自拔&#xff0c;你能想想得到&#xff0c;一个完全不会画画的有一天也能创作出绘…

【测试基础01】

软件测试 一、软件测试基本概念(1)、软件测试的定义(2)、软件错误的定义(3)、测试分类 二、需求文档的评审三、软件测试计划(1)、测试范围(2)、测试环境(3)、测试策略(4)、测试管理(5)、测试风险(6)、模板 一、软件测试基本概念 (1)、软件测试的定义 软件测试是从前期需求文档…

freeswitch 使用 silero-vad 静音拆分使用 fastasr 识别

silero-vad 在git 的评分挺高的测试好像比webrtc vad好下面测试下 silero-vad 支持c 和py 由于识别c的框架少下面使用py 以下基于python3.8torch1.12.0torchaudio 1.12.0 1.由于fastasr 需要16k 所以 将freeswitch的实时音频mediabug 8k转成16k 用socket传到py 模块代码…

ChatGLM-6B 在 ModelWhale和本地 平台的部署与微调教程

ChatGLM-6B 在 ModelWhale 平台的部署与微调教程 工作台 - Heywhale.com ChatGLM-6B 介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费…

高压放大器在介电材料测试中的应用

介电材料测试是一项重要的材料性能测试&#xff0c;它涉及到物理学、化学、材料科学等多个学科领域。高压放大器是介电材料测试中的一种重要设备&#xff0c;它可以放大微弱的电信号&#xff0c;提高测试的准确性和精度。下面将详细介绍高压放大器在介电材料测试中的应用。 图&…

Web前端开发技术储久良第三版课后选择答案(1-10章)

P16-第1章 练习与实验答案 练习1 1.选择题 【1】Html是一种&#xff08;&#xff09;语言。 【A】编译型 【B】超文本标记 【C】高级程序设计 【D】面向对象编程【2】世界上第一个网页是()。 【A】http://www.w3c.org 【B】http:/info.cern.ch 【C】http://www.microsoft.com…

【论文阅读】(2023.06.09-2023.06.18)论文阅读简单记录和汇总

(2023.06.09-2023.06.12)论文阅读简单记录和汇总 2023/06/09&#xff1a;虽然下周是我做汇报&#xff0c;但是到了周末该打游戏还是得打的 2023/06/12&#xff1a;好累好困&#xff0c;现在好容易累。 目录 &#xff08;TCSVT 2023&#xff09;Facial Image Compression via …

2021电工杯数学建模B题解题思路

目录 一、前言 二、问题背景 三、具体问题 四、解题思路 &#xff08;一&#xff09;整体思路 &#xff08;二&#xff09;问题一 &#xff08;三&#xff09;问题二 &#xff08;四&#xff09;问题三 &#xff08;五&#xff09;问题四 &#xff08;六&#xff09;…

使用parcel搭建threejs开发环境

一、什么是parcel parcel官网&#xff1a;https://www.parceljs.cn/ Parcel是一个快速、零配置的Web应用打包器&#xff0c;可将JavaScript、CSS、HTML和图像等静态文件打包到一个捆绑文件中。它的主要目标是简化Web应用程序的打包过程&#xff0c;使开发人员可以更快速地创建…

【深度学习】1 感知机(人工神经元)

认识感知机 感知机接收多个输入信号&#xff0c;输出一个信号 感知机的信号只有“流/不流”(1/0)两种取值 0对应“不传递信号”&#xff0c;1对应“传递信号”。 输入信号被送往神经元时&#xff0c;会被分别乘以固定的权重。神经元会计算传送过来的信号的综合&#xff0c;只有…

怎么利用代理IP优化网络爬虫

网络爬虫会自动扫描互联网&#xff0c;搜集大量数据并将它们组织起来。但是&#xff0c;许多网站都采取了反爬虫策略&#xff0c;限制了网络爬虫的活动。这时候&#xff0c;代理IP就起到了关键作用。 一、代理ip在网络爬虫中的作用 代理ip爬虫中使用代理IP有很多好处。首先&…

OpenCV 笔记_3

文章目录 笔记_3直方图匹配(直方图规定化) 主要针对单通道图像模板匹配matchTemplate 模板匹配函数 图像卷积filter2D 卷积函数 过滤器图像噪声的产生cvflann::rand_double 产生随机浮点数在&#xff08;0~1&#xff09;之间cvflann::rand_int 产生随机整数在&#xff08;0~RAN…

golang实现webgis后端开发

目录 前言 二、实现步骤 1.postgis数据库和model的绑定 2.将pg库中的要素转换为geojson (1)几何定义 (2)将wkb解析为几何类型 (3)定义geojson类型 (4)数据转换 &#xff08;5&#xff09;数据返回 2.前端传入的geojson储存到数据库 3、其他功能实现 总结 前言 停更了…

系列二、RuoYi前后端分离(登录密码加密去除公钥)

一、问题描述 系列一虽然实现了登录时密码加密&#xff0c;但是/getPublicKey返回的结果中&#xff0c;把私钥也返回了&#xff0c;这样显然是不合理的&#xff0c;如下&#xff1a; 二、后端代码修改 2.1、RSAUtil package com.tssl.business.utils;import org.apache.comm…

【自监督论文阅读 2】MAE

文章目录 一、摘要二、引言2.1 引言部分2.2 本文架构 三、相关工作3.1 Masked language modeling3.2 Autoencoding3.3 Masked image encoding3.4 Self-supervised learning 四、方法4.1 Masking4.2 MAE encoder4.3 MAE decoder4.4 Reconstruction target 五、主要实验5.1 不同m…

flutter:数据持久化

简单的数据持久化 保存数据到本地磁盘是应用程序常用功能之一&#xff0c;比如保存用户登录信息、用户配置信息等。而保存这些信息通常使用 shared_preferences&#xff0c;它保存数据的形式为 Key-Value&#xff08;键值对&#xff09;&#xff0c;支持 Android 和 iOS。shar…

访问者模式(十八)

相信自己&#xff0c;请一定要相信自己 上一章简单介绍了命令模式(十七), 如果没有看过, 请观看上一章 一. 访问者模式 引用 菜鸟教程里面访问者模式介绍: https://www.runoob.com/design-pattern/visitor-pattern.html 在访问者模式&#xff08;Visitor Pattern&#xff09…

K8s in Action 阅读笔记——【14】Securing cluster nodes and the network

K8s in Action 阅读笔记——【14】Securing cluster nodes and the network 迄今为止&#xff0c;创建了 Pod 而不考虑它们允许消耗多少 CPU 和内存。但是&#xff0c;正如将在本章中看到的那样&#xff0c;设置 Pod 预期消耗和允许消耗的最大数量是任何 Pod 定义的重要部分。…