【Spring源码】一:整体流程

2023-10-26

总流程 12 个方法

// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
	// Allows post-processing of the bean factory in context subclasses.
	postProcessBeanFactory(beanFactory);

	// Invoke factory processors registered as beans in the context.
	invokeBeanFactoryPostProcessors(beanFactory);

	// Register bean processors that intercept bean creation.
	registerBeanPostProcessors(beanFactory);

	// Initialize message source for this context.
	initMessageSource();

	// Initialize event multicaster for this context.
	initApplicationEventMulticaster();

	// Initialize other special beans in specific context subclasses.
	onRefresh();

	// Check for listener beans and register them.
	registerListeners();

	// Instantiate all remaining (non-lazy-init) singletons.
	finishBeanFactoryInitialization(beanFactory);

	// Last step: publish corresponding event.
	finishRefresh();
}

1. prepareRefresh

prepareRefresh:为上下文对象初始化一些状态与属性,为后面的工作做准备。会设置容器的启动时间,设置活跃状态为ture。设置关闭状态为false,获取Enviroment对象设置到容器中,准备监听器和时间的集合对象,默认为空。

在这里插入图片描述

  • initPropertySources() :给用户重写扩展,可以初始化一些属性资源,springMVC对其进行了扩展
  • validateRequiredProperties() :这个方法是对一些启动必须的属性的验证。
    我们可以通过实现或者继承 ApplicationContext 来重写这两个方法,从而完成一些基本属性的校验。

2. obtainFreshBeanFactory

实现Context的BeanFactory功能,执行完这个函数后。Context才真正拥有了BeanFactory的功能

  • 会首先创建一个DefaultListableBeanFactory 工厂,设置序列化id
  • 将XML内容解析为BeanDefinitions

在这里插入图片描述

if (hasBeanFactory()) {
	destroyBeans();
	closeBeanFactory();
}
try {
	// 会首先创建一个`DefaultListableBeanFactory` 工厂
	DefaultListableBeanFactory beanFactory = createBeanFactory();
	// 为工厂设置序列化id,可以反序列化到工厂对象
	beanFactory.setSerializationId(getId());
	//定制工厂,是否允许覆盖同名不同定义的对象及循环依赖
	customizeBeanFactory(beanFactory);
	//将XML内容解析为BeanDefinitions
	loadBeanDefinitions(beanFactory);
	this.beanFactory = beanFactory;
}
catch (IOException ex) {
	throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}

定制工厂,是否允许覆盖同名不同定义的对象及循环依赖:需要继承重写customizeBeanFactory方法,设置allowBeanDefinitionOverriding,allowCircularReferences

2.1 createBeanFactory

创建一个类型为DefaultListableBeanFactory的beanFactory

DefaultListableBeanFactory 是整个springbean加载的核心部分在这里插入图片描述
之所以有这么多接口,大概是接口隔离原则,最小接口拆分
最顶层的三个接口

  • BeanFactory:定义获取单个bean或其属性(getBean,isSingleton)
  • SingletonBeanRegistry:定义对单例的注册及获取(registerSingleton)
  • AliasRegistry:定义对alias的增删改查(registerAlias)

另外的一些接口

  • HierarchicalBeanFactory:增加对parentBeanFactory的获取方法(getParentBeanFactory)ListableBeanFactory:增加根据各种条件获取BeanDefinition集合方法(getBeanNamesForType)
  • ConfigurableBeanFactory:增加配置BeanFactory方法(setParentBeanFactory)
  • AutowireCapableBeanFactory:增加创建Bean(createBean),自动注入(autowireBean),应用Bean的后置处理器(applyBeanPostProcessorsBeforeInitialization,applyBeanPostProcessorsAfterInitialization)

2.2 loadBeanDefinitions

创建XmlBeanDefinitionReader来读取XML配置文件变为BeanDefinition

在这里插入图片描述

  1. 获取XML文件转为Document实例对象。

  2. 解析标签之前也会先去对profile解析(这个可以让我们方便的进行开发,部署环境的切换)。

  3. Spring中有两大类声明,一种是默认的(import bean等);另一个是自定的(aop,tx等)handler进行解析,自定义标签需要写入spring.handlers来查找自定义的handler,通过handler解析写入BeanDefinition。

spring.handlers内容如下,soring会加载配置文件并记载到map中,方便寻找handler。
在这里插入图片描述

BeanDefinition其实是XML bean的信息转化为容器的内部表示。BeanDefinition都以Map的结构存入BeanDefinitionRegistry(两部分注册,别名和beanName),后续crud都是通过这个类在这里插入图片描述

  • 配置文件bean标签有class,scope,lazy-init ; BeanDefinition中也同样有
  • AbstractBeanDefinition是对公用信息的抽象
  • 配置文件中bean标签可以配置子bean标签,父子关系,那么在BeanDefinition中父用RootBeanDefinition,子用ChildBeanDefinition,没有父就用RootBeanDefinition。

xsd来定义xml schenas的规范,所以在解析XML之前需要解析到需要的xsd,往往写xml时xsd地址是网络地址,这样断网就无法正常运行,会有一份本地的xsd存储在映射在引入的JAR中 MATA-INF有spring.schemas 下
在这里插入图片描述

<context:component-scan base-package=“org.hhh” annotation-config=“true”/> component-scan 标签的 annotation-config="true"会注入一下对象的beandefinition
在这里插入图片描述

解析代码如下

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
				//默认标签解析
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}
				//自定义标签解析
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}

3. prepareBeanFactory

创建出工厂后在此处设置工厂
在这里插入图片描述

  • SPEL:可以使用#{…}作为定界符,构建复杂表达式,方法调用
  • 属性编辑器: XML中< value >标签到spring是string类型,如果注入的对象是Date类型,就会报错。我们就可以继承PropertyEditorSupport重写setAsText来保证类型匹配,然后注入到spring中即刻
  • ApplicationContextAwareProcessor:没有看懂此处
  • ignoreDependencyInterface:忽略某些接口,当需要Spring注入时忽略依赖

4. postProcessBeanFactory

模板方法,子类重写

5. invokeBeanFactoryPostProcessors

在这里你可以对beandefinition进行处理,比如解析修改。

5.1 BeanFactoryPostProcessor的执行顺序

在这里插入图片描述

  • 对于beanFactoryPostProcessor分两种情况进行,一个是对于BeanDefinitionRegistryostProcessor类的处理,另一种是对普通BeanFactoryPostProcessor类的处理
  • 处理顺序是先处理外部定义的集合,之后是子类,最后是父类。遇到子类型会执行postProcessBeanDefinitionRegistry方法之后加入父类的集合,最后与父类一起执行postProcessorBeanFactory方法
  • 子类和父类的集合每个元素的执行方法也有顺序,先处理继承PriorityOrdered的类,再处理继承Ordered的类,再处理没有之前两个接口的类

注:上面图示大致流程,有更多细节,最后完成了执行;PriorityOrdered,Ordered 也有权重所以需要sort

5.2 重要的两个BFPP

其中重要的两个BeanFactoryPostProcessor,

5.2.1 ConfigurationClassPostProcessor

首先我们要知道这个BFPP哪里加载进来的,在loadBeanDefinnation

<context:component-scan base-package="org.djh" annotation-config="true"/>  

这个标签解析的时候放入了一个beanDefinition internalConfigurationAnnotationProcessor所对应实例为ConfigurationClassPostProcessor,这个继承了BeanDefinitionRegistryostProcessor,执行postProcessBeanDefinitionRegistry就开始解析

在这里插入图片描述

ConfigurationClassPostProcessor用于解析被注解(@Configuration @ComponentScan @Import @Bean等)修饰。
首先会拿到候选的beanDefinition,筛选出可以被ConfigurationClassParser处理的beanDefinition,筛选删选逻辑如下:

Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
//含有Configuration且包含proxyBeanMethods属性为FULL
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//含有Configuration或者含有Component,ComponentScan,Import,ImportResource为LITE,否则就不处理
else if (config != null || isConfigurationCandidate(metadata)) {
	beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
//否则不处理
else {
	return false;
}

其中复杂的就是@Import ,就顺便说下springboot的装配过程

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

在这里插入图片描述

首先就是上面说的

internalConfigurationAnnotationProcessor所对应实例为ConfigurationClassPostProcessor生成解析@Import注解
在这里插入图片描述
getImports(sourceClass) 是一个递归调用 ,就会得到一个注册类的的集合(ImportSelector,ImportBeanDefinitionRegistar 这些注册BeanDefinition),所以得到Registrar 和 AutoConfigurationImportSelector两个类
得到之后,就能根据注册类集合,根据其中不同类型调用他用注册beanDefinition的方法,如下

for (SourceClass candidate : importCandidates) {
	if (candidate.isAssignable(ImportSelector.class)) {
		// Candidate class is an ImportSelector -> delegate to it to determine imports
		...
		else {
			String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
			...
		}
	}
	else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
		// Candidate class is an ImportBeanDefinitionRegistrar ->
		// delegate to it to register additional bean definitions
		...
		configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
	}
	else {
		// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
		// process it as an @Configuration class
		...
	}
}

调用方法之后就能将我们想要注册的注册进去

5.2.2 PlaceholderConfigurationSupport用于处理被${}修饰的beanDefinition

6. registerBeanPostProcessors

这里与invokeBeanFactoryPostProcessors类似,但是只是注册BeanPostProcessor,实际的调用再构造bean的步骤。
在这里插入图片描述

7 8 9 10 .

在这里插入图片描述

initMessageSource
initApplicationEventMulticaster
onRefresh
registerListeners

11. finishBeanFactoryInitialization

在这里插入图片描述

这里就是创建bean的过程,先尝试获取bean(getBean),获取不到createBean的流程如下
在这里插入图片描述
BeanFactoryPostProcessor是操作未实例化beanDefinition的,那么BeanPostProcessor化后的实例对象。

BeanPostProcessor会根据对象信息进行判断,看是否需要执行applyBeanPostProcessorsBeforeInitialization或者applyBeanPostProcessorsAfterInitialization比如

  • AOP中AbstractAutoProxyCreator主要根据shouldSkip(bean.getClass(), beanName),这个方法主要根据所有advisor,根据切入表达式来计算切入的类是否与当前beanName一致,一旦有一个就说明需要执行
  • ApplicationContextAwareProcessor,主要根据beanName会判断是否为某些aware(ApplicationContextAware,beanNameAware)就执行invokeAwareMethod
    下面是springMVC中的一个重要应用,其中AbstractHandlerMethodMapping继承了ApplicationContextAware就会在实例化的时候执行对应方法,而它的效果会注册 url到controller的映射到handlerMap,这样在springMVC中为接受请求时url寻找controller时候就能直接找(不过这个类解决继承Controller和HTTPRequestHandler的Controller,不是解决有@Controller表明的Controller)
    在这里插入图片描述

12.finishRefresh

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Spring源码】一:整体流程 的相关文章

  • spring webflow,评估表达式在转换中被忽略

    我有一个流定义为流
  • 来自数据库的 jfreechart 散点图

    如何使用java中的jfreechart绘制mysql数据库表中数据的散点图 我使用过 Swing 库 任何链接都会有帮助 我搜索了谷歌但找不到理解的解决方案 如果您有代码 请提供给我 实际上我确实做了条形图并使用 jfreechart 绘
  • Java:使用 HttpURLConnection 的 HTTP PUT

    如何执行 HTTP PUT 我正在使用的类似乎认为它正在执行 PUT 但端点将其视为我执行了 GET 我做错了什么吗 URL url new URL https HttpURLConnection conn HttpURLConnectio
  • 使用 RecyclerView 适配器在运行时更改布局屏幕

    我有两个布局文件 如下所示 如果列表中存在数据 则我显示此布局 当列表为空时 我会显示此布局 现在我想在运行时更改布局 当用户从列表中删除最后一项时 我想将布局更改为第二张图片中显示的 空购物车布局 In getItemCount Recy
  • Java套接字:在连接被拒绝异常时重试的最佳方法?

    现在我正在这样做 while true try SocketAddress sockaddr new InetSocketAddress ivDestIP ivDestPort downloadSock new Socket downloa
  • 如果使用的 JVM 是 x86 或 x64,则以不同的方式解决 Maven 依赖关系?

    我设置了一个 Maven 存储库来托管一些 dll 但我需要我的 Maven 项目根据使用的 JVM 是 x86 还是 x64 下载不同的 dll 例如 在运行 x86 版本 JVM 的计算机上 我需要从存储库下载 ABC dll 作为依赖
  • 内存一致性 - Java 中的happens-before关系[重复]

    这个问题在这里已经有答案了 在阅读有关内存一致性错误的 Java 文档时 我发现与创建 发生 之前 关系的两个操作相关的点 当语句调用时Thread start 每个具有 与该语句发生之前的关系也有一个 与 new 执行的每个语句之间发生的
  • 如何将 android.net.Uri 转换为 java.net.URL? [复制]

    这个问题在这里已经有答案了 有没有办法从Uri to URL 我正在使用的库需要这个 它only接受一个URL但我需要在我的设备上使用图像 如果该方案的Uri is http or https new URL uri toString 应该
  • 具有共享依赖项的多模块项目的 Gradle 配置

    使用 gradle 制作第一个项目 所以我研究了 spring gradle hibernate 项目如何组织 gradle 文件 并开始制作自己的项目 但是 找不到错误 为什么我的配置不起作用 子项目无法解决依赖关系 所以项目树 Root
  • Java:正则表达式排除空值

    在问题中here https stackoverflow com questions 51359056 java regexp for a separated group of digits 我得到了正则表达式来匹配 1 到 99 之间的一
  • Java 服务器-客户端 readLine() 方法

    我有一个客户端类和一个服务器类 如果客户端向服务器发送消息 服务器会将响应发送回客户端 然后客户端将打印它收到的所有消息 例如 如果客户端向服务器发送 A 则服务器将向客户端发送响应 1111 所以我在客户端类中使用 readLine 从服
  • 用于从字段中查找最大值的 MongoTemplate 方法或查询

    我正在使用 MongoTemplate 进行数据库操作 现在我想从所选结果中获取最大字段值 有人可以指导我如何编写查询 以便当我将查询传递给 find 方法时 它将返回我所需的文档最大字段 提前致谢 问候 可以在spring data mo
  • RSA OAEP、Golang 加密、Java 解密 -BadPaddingException:解密错误

    我正在尝试解密使用 RSA OAEP 在 Golang 中加密的字符串 但出现 BadPaddingException 解密错误 很难弄清楚我错过了什么 这是Golang加密方法 func encryptString rootPEM io
  • 计算日期之间的天数差异

    在我的代码中 日期之间的差异是错误的 因为它应该是 38 天而不是 8 天 我该如何修复 package random04diferencadata import java text ParseException import java t
  • Cloudfoundry:如何组合两个运行时

    cloundfoundry 有没有办法结合两个运行时环境 我正在将 NodeJS 应用程序部署到 IBM Bluemix 现在 我还希望能够执行独立的 jar 文件 但应用程序失败 APP 0 bin sh 1 java not found
  • 如何在 Eclipse Java 动态 Web 项目中使用 .properties 文件?

    我正在 Eclipse 中开发动态 Web 项目 我创建了一个 properties 文件来存储数据库详细信息 用户名 密码等 我通过右键单击项目和 New gt File 添加它 我使用了Java util包Properties类 但它不
  • Java的-XX:+UseMembar参数是什么

    我在各种地方 论坛等 看到这个参数 并且常见的答案是它有助于高并发服务器 尽管如此 我还是找不到 sun 的官方文档来解释它的作用 另外 它是Java 6中添加的还是Java 5中存在的 顺便说一句 许多热点虚拟机参数的好地方是这一页 ht
  • Java:多线程内的 XA 事务传播

    我如何使用事务管理器 例如Bitronix http docs codehaus org display BTM Home JBoss TS http www jboss org jbosstm or Atomikos http www a
  • Android - 9 补丁

    我正在尝试使用 9 块图片创建一个新的微调器背景 我尝试了很多方法来获得完美的图像 但都失败了 s Here is my 9 patch 当我用Draw 9 patch模拟时 内容看起来不错 但是带有箭头的部分没有显示 或者当它显示时 这部
  • Java &= 运算符应用 & 或 && 吗?

    Assuming boolean a false 我想知道是否这样做 a b 相当于 a a b logical AND a is false hence b is not evaluated 或者另一方面 这意味着 a a b Bitwi

随机推荐

  • 【对比Java学Kotlin】作用域函数

    什么是作用域函数 首先 落脚点是函数 什么的函数呢 能在某个上下文对象 可能是普通对象 也可能是个 Unit 的作用域内执行代码的函数 这里的作用域和 Java 的作用域有所不同 Java 的作用域更多的是指一对闭合的 的内部区域 void
  • Oracle数据库环境变量配置+修改数据库密码

    1 设置环境变量 必须设置环境变量才可以用CMD命令访问Oracle数据库 1 1 首先找到你Oracle安装位置路径 C app Administrator product 11 2 0 dbhome 1 1 2 设置环境变量 1 2 1
  • RAM汇编指令的简单介绍

    一些常见的概念 指令和伪指令 指令和伪指令的本质区别 指令在编译后会生成机器码由CPU去执行 而伪指令只是指导编译的过程 本身不会生成机器码 伪指令是编译器提供的 在编译的过程中 编译器自己会去对伪指令做相应的处理 什么是汇编指令 汇编指令
  • mysql installer no packages found 解决办法

    mysql installer no packages found 解决办法 方法源于https stackoverflow com questions 65772865 mysql installer did not find packa
  • 网络层协议和IP数据包的格式(详解)

    1 网络层的功能 定义了基于IP协议的逻辑地址 就是ip地址 连接不同的媒介类型 选择数据通过网络的最佳路径 完成逻辑地址寻址 2 IP数据包格式 版本号 Version 4bit 指IP协议版本 并且通信双方使用的版本必须保持一致 目前我
  • chatGPT是什么?chatGPT有哪些应用场景

    ChatGPT是一个通过大规模预训练语言模型实现的对话生成系统 它是由OpenAI研发的 这个系统的核心技术是基于深度学习的自然语言处理技术 GPT GenerativePre trainedTransformer 模型 GPT模型采用了T
  • uni-app中的#ifdef #ifndef #endif的用处,可以处理兼容多端平台

    1 ifdef ifndef endif三者的意思 ifdef 仅在某个平台上使用 ifndef 在除了这个平台的其他平台上使用 非此平台使用 endif 结束条件编译 2 平台标识的意思 标识 平台 APP PLUS 5 App MP 微
  • 关于vmware虚拟机的deepin没有网络问题

    首先我的vmware虚拟机的deepin是采用NAT模式 配置NAT网络 配置VMnet8 在deepin中手动添加有线连接 添加设置 保存退出 用有线连接2即可
  • 微信小程序实践——实验2天气查询小程序

    一 申请和风天气的API密钥 登录和风天气 创建成功后可得到自己的key 二 域名的配置 每一个小程序在与指定域名地址进行网络通信前都必须将该域名地址添加到管理员后台白名单中 需对域名地址https devapi qweather com进
  • Search for a Range(LeetCode)

    Given an array of integers nums sorted in ascending order find the starting and ending position of a given target value
  • 【python】爬虫篇:python使用psycopg2批量插入数据(三)

    本人菜鸡 有什么错误 还望大家批评指出 最近在更新python的爬虫系列 皿 Hiahiahia 该系列暂时总共有3篇文章 连接如下 python 爬虫篇 python连接postgresql 一 https blog csdn net l
  • 查看系统使用率命令 vmstat 输出详解!

    使用效果
  • 凸包问题的五种解法

    原文 http blog csdn net yangkunpengd article details 51336453 凸包问题的五种解法 前言 首先 什么是凸包 说凸包首先要说凸性的定义 简单点说就是平面邻域中任意两点所在的线段上的点都在
  • LR(0)文法分析(通过例题穿插讲解)

    目录 LR 0 文法的字面含义 LR 0 分析表的构造 写在最后 LR 0 文法的字面含义 LR 0 分析法是其他LR分析法构造的基础 L表示从左往右扫描 R表示反向构造出一个最右推导 k表示向前看k个字符 缺省为1 在学习LR 0 分析时
  • Linux驱动框架与LED实战

    目录 驱动框架 相关文件 案例分析 LED驱动框架源码 led class c led class attrs leds class class结构体 led classdev register 某一类的设备创建 led classdev结
  • QT获取显示当前时间和日期

    获取当前时间和日期 QT中获取时间和日期的主要是 QTime QDate 和 QDateTime 这三个类 QTime 类 通过 QTime 类中提供的时间相关的方法 可以获取到当前系统时间 时 分 秒 毫秒 需要注意的是 计时的准确性由底
  • QWidget/QDialog主窗体设置边框圆角

    1 问题 QT中窗体QWidget和QDialog为容器 不能对窗体进行边框圆角样式改变 只能通过绘图QPainter 2 设置无上边框选项窗口 this gt setWindowFlags Qt Widget Qt FramelessWi
  • CSS学习笔记八——宽高自适应

    宽高自适应 一 宽度自适应 二 高度自适应 三 浮动元素的高度自适应 四 窗口自适应 五 结语 一 宽度自适应 不写宽度或者写 width auto就表示宽度自适应 可用于横栏或导航栏 与 width 100 不同 设为100 已经固定了宽
  • MySQL之无限级分类表设计

    首先查找一下goods cates表和table goods brands数据表 分别使用命令 root localhost test gt show columns from goods cates root localhost test
  • 【Spring源码】一:整体流程

    总流程 12 个方法 Prepare this context for refreshing prepareRefresh Tell the subclass to refresh the internal bean factory Con