Spring IOC 之加载 BeanDefinition

1、前言

前面的文章我们已经对IOC之Spring统一资源加载策略有了一定的了解,本文我们将探讨Spring IOC 加载 BeanDefinition的整个过程。

我们先先看一段熟悉的代码:

ClassPathResource resource = new ClassPathResource("bean.xml"); // <1>
DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // <2>
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // <3>
reader.loadBeanDefinitions(resource); // <4>

这段代码是 Spring 中编程式使用 IoC 容器,通过这四段简单的代码,我们可以初步判断 IoC 容器的使用过程。

  1. 获取资源
  2. 获取 BeanFactory
  3. 根据新建的 BeanFactory 创建一个 BeanDefinitionReader 对象,该 Reader 对象为资源的解析器
  4. 装载资源

整个过程就分为三个步骤:资源定位、装载、注册,如下:

 

1、资源定位。我们一般用外部资源来描述 Bean 对象,所以在初始化 IoC 容器的第一步就是需要定位这个外部资源。在上一篇博客已IOC之Spring统一资源加载策略经详细说明了资源加载的过程。

2、装载。装载就是 BeanDefinition 的载入。BeanDefinitionReader 读取、解析 Resource 资源,也就是将用户定义的 Bean 表示成 IoC 容器的内部数据结构:BeanDefinition 。

  • 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构
  • 在配置文件中每一个 <bean> 都对应着一个 BeanDefinition 对象。

3、注册。向 IoC 容器注册在第二步解析好的 BeanDefinition,这个过程是通过 BeanDefinitionRegistry 接口来实现的。在 IoC 容器内部其实是将第二个过程解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap 来维护这些 BeanDefinition 的。

  • 在这里需要注意的一点是这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用 #getBean(...) 方法,向容器索要 Bean 时。
  • 当然我们可以通过设置预处理,即对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化的时候完成。

2 源码分析

2.1 Resource 定位

Spring 为了解决资源定位的问题,提供了两个接口:Resource、ResourceLoader,其中:

  • Resource 接口是 Spring 统一资源的抽象接口
  • ResourceLoader 则是 Spring 资源加载的统一抽象。

Resource 资源的定位需要 Resource 和 ResourceLoader 两个接口互相配合,在上面那段代码中 new ClassPathResource("bean.xml") 为我们定义了资源,那么 ResourceLoader 则是在什么时候初始化的呢?看 XmlBeanDefinitionReader 构造方法:

// XmlBeanDefinitionReader.java
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {super(registry);
}
  • 直接调用父类 AbstractBeanDefinitionReader 构造方法,代码如下:
    // AbstractBeanDefinitionReader.javaprotected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;// Determine ResourceLoader to use.if (this.registry instanceof ResourceLoader) {this.resourceLoader = (ResourceLoader) this.registry;}	else {this.resourceLoader = new PathMatchingResourcePatternResolver();}// Inherit Environment if possibleif (this.registry instanceof EnvironmentCapable) {this.environment = ((EnvironmentCapable) this.registry).getEnvironment();} else {this.environment = new StandardEnvironment();}
    }
  • 核心在于设置 resourceLoader 这段,如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver ,该类是一个集大成者的 ResourceLoader。

2.2 BeanDefinition 的载入和解析

reader.loadBeanDefinitions(resource); 代码段,开启 BeanDefinition 的解析过程。如下:

// XmlBeanDefinitionReader.java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource));
}

在这个方法会将资源 resource 包装成一个 EncodedResource 实例对象,然后调用 #loadBeanDefinitions(EncodedResource encodedResource) 方法。而将 Resource 封装成 EncodedResource 主要是为了对 Resource 进行编码,保证内容读取的正确性。代码如下:

// XmlBeanDefinitionReader.javapublic int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {// ... 省略一些代码try {// 将资源文件转为 InputStream 的 IO 流InputStream inputStream = encodedResource.getResource().getInputStream();try {// 从 InputStream 中得到 XML 的解析源InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// ... 具体的读取过程return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}// 省略一些代码
}

从 encodedResource 源中获取 xml 的解析源,然后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行具体的解析过程。

// XmlBeanDefinitionReader.javaprotected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 获取 XML Document 实例Document doc = doLoadDocument(inputSource, resource);// 根据 Document 实例,注册 Bean 信息int count = registerBeanDefinitions(doc, resource);return count;}// ... 省略一堆配置
}
  • 在该方法中主要做两件事:
  • 1、根据 xml 解析源获取相应的 Document 对象。
  • 2、调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,开启 BeanDefinition 的解析注册过程。

2.3  转换为 Document 对象

调用 #doLoadDocument(InputSource inputSource, Resource resource) 方法,会将 Bean 定义的资源转换为 Document 对象。代码如下:

// XmlBeanDefinitionReader.javaprotected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}

该方法接受五个参数:

  • inputSource :加载 Document 的 Resource 源。
  • entityResolver :解析文件的解析器。
  • errorHandler :处理加载 Document 对象的过程的错误。
  • validationMode :验证模式。
  • namespaceAware :命名空间支持。如果要提供对 XML 名称空间的支持,则为 true 。

#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,在类 DefaultDocumentLoader 中提供了实现。代码如下:

// DefaultDocumentLoader.java@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 创建 DocumentBuilderFactoryDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);// 创建 DocumentBuilderDocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 解析 XML InputSource 返回 Document 对象return builder.parse(inputSource);
}

2.4 注册 BeanDefinition 流程

这到这里,就已经将定义的 Bean 资源文件,载入并转换为 Document 对象了。那么,下一步就是如何将其解析为 SpringIoC 管理的 BeanDefinition 对象,并将其注册到容器中。这个过程由方法 #registerBeanDefinitions(Document doc, Resource resource) 方法来实现。代码如下:

// XmlBeanDefinitionReader.javapublic int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 创建 BeanDefinitionDocumentReader 对象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 获取已注册的 BeanDefinition 数量int countBefore = getRegistry().getBeanDefinitionCount();// 创建 XmlReaderContext 对象// 注册 BeanDefinitiondocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 计算新注册的 BeanDefinition 数量return getRegistry().getBeanDefinitionCount() - countBefore;
}
  • 首先,创建 BeanDefinition 的解析器 BeanDefinitionDocumentReader 。
  • 然后,调用该 BeanDefinitionDocumentReader 的 #registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,开启解析过程,这里使用的是委派模式,具体的实现由子类 DefaultBeanDefinitionDocumentReader 完成。代码如下:

    // DefaultBeanDefinitionDocumentReader.java@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;// 获得 XML Document Root Element// 执行注册 BeanDefinitiondoRegisterBeanDefinitions(doc.getDocumentElement());
    }

    2.4.1 对 Document 对象的解析

    从 Document 对象中获取根元素 root,然后调用 #doRegisterBeanDefinitions(Element root)` 方法,开启真正的解析过程。代码如下:

    // DefaultBeanDefinitionDocumentReader.javaprotected void doRegisterBeanDefinitions(Element root) {// ... 省略部分代码(非核心)this.delegate = createDelegate(getReaderContext(), root, parent);// 解析前处理preProcessXml(root);// 解析parseBeanDefinitions(root, this.delegate);// 解析后处理postProcessXml(root);}

     

  • #preProcessXml(Element root)#postProcessXml(Element root) 为前置、后置增强处理,目前 Spring 中都是空实现。
  • #parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) 是对根元素 root 的解析注册过程。代码如下:

    // DefaultBeanDefinitionDocumentReader.javaprotected 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);}
    }
  • 迭代 root 元素的所有子节点,对其进行判断:
    • 若节点为默认命名空间,则调用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,开启默认标签的解析注册过程。
    • 否则,调用 BeanDefinitionParserDelegate#parseCustomElement(Element ele) 方法,开启自定义标签的解析注册过程。

2.4.1.1 默认标签解析

若定义的元素节点使用的是 Spring 默认命名空间,则调用 #parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) 方法,进行默认标签解析。代码如下:

// DefaultBeanDefinitionDocumentReader.javaprivate void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // importimportBeanDefinitionResource(ele);} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // aliasprocessAliasRegistration(ele);} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // beanprocessBeanDefinition(ele, delegate);} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans// recursedoRegisterBeanDefinitions(ele);}
}

对四大标签:<import><alias><bean><beans> 进行解析。其中 <bean> 标签的解析为核心工

2.4.1.2 自定义标签解析

对于默认标签则由 parseCustomElement(Element ele) 方法,负责解析。代码如下:

// BeanDefinitionParserDelegate.java@Nullable
public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null);
}@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {// 获取 namespaceUriString namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据 namespaceUri 获取相应的 HandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 调用自定义的 Handler 处理return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

获取节点的 namespaceUri,然后根据该 namespaceUri 获取相对应的 NamespaceHandler,最后调用 NamespaceHandler 的 #parse(Element element, ParserContext parserContext) 方法,即完成自定义标签的解析和注入。

2.4 注册 BeanDefinition

经过上面的解析,则将 Document 对象里面的 Bean 标签解析成了一个个的 BeanDefinition ,下一步则是将这些 BeanDefinition 注册到 IoC 容器中。动作的触发是在解析 Bean 标签完成后,代码如下:

// DefaultBeanDefinitionDocumentReader.javaprotected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 进行 bean 元素解析。// 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象// 如果解析失败,则返回 null 。BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 进行自定义标签处理bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 进行 BeanDefinition 的注册// Register the final decorated instance.BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());} catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。// Send registration event.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}
}

调用 BeanDefinitionReaderUtils.registerBeanDefinition() 方法,来注册。其实,这里面也是调用 BeanDefinitionRegistry 的 #registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法,来注册 BeanDefinition 。不过,最终的实现是在 DefaultListableBeanFactory 中实现,代码如下:

// DefaultListableBeanFactory.java
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {// ...省略校验相关的代码// 从缓存中获取指定 beanName 的 BeanDefinitionBeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);// 如果已经存在if (existingDefinition != null) {// 如果存在但是不允许覆盖,抛出异常if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);} else {// ...省略 logger 打印日志相关的代码}// 【重点】允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。this.beanDefinitionMap.put(beanName, beanDefinition);// 如果未存在} else {// ... 省略非核心的代码// 【重点】添加到 BeanDefinition 到 beanDefinitionMap 中。this.beanDefinitionMap.put(beanName, beanDefinition);}// 重新设置 beanName 对应的缓存if (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}
}
  • 这段代码最核心的部分是这句 this.beanDefinitionMap.put(beanName, beanDefinition) 代码段。所以,注册过程也不是那么的高大上,就是利用一个 Map 的集合对象来存放:key 是 beanName ,value 是 BeanDefinition 对象。

3.总结

至此,整个 IoC 的初始化过程就已经完成了,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IoC 容器中,都已经完美地完成了。现在 IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。最后用一张流程图来结束这篇总结之文。

be675f310bb74b9cab0409f11d9ccccf.png

 

 

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

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

相关文章

USB8814动态信号采集卡——声音振动类信号处理的理想之选!

背景介绍&#xff1a; 科技的发展在一定程度上依赖于对信号的处理&#xff0c;信号处理技术的先进性在很大程度上决定了科技发展的速度和方向。数字信号处理技术的崛起&#xff0c;彻底改变了传统的信息与信号处理方式&#xff0c;使得数据采集这一前期工作在数字系统中发挥着…

Oracle-java下载、开源/商业许可证(收费、免费说明)、版本发布日志

Oracle-java下载、开源/商业许可证&#xff08;收费、免费说明&#xff09;、版本发布日志 下载开源/商业许可证&#xff08;收费、免费说明&#xff09;java8版本发布日志以上是一般情况&#xff0c;具体的以官网发布信息为准例如&#xff1a; JDK17某些特定版本是免费的&…

极狐GitLab 线下『 DevOps专家训练营』成都站开班在即

成都机器人创新中心联合极狐(GitLab)隆重推出极狐GitLab DevOps系列认证培训课程。该课程主要面向使用极狐GitLab的DevOps工程师、安全审计人员、系统运维工程师、系统管理员、项目经理或项目管理人员&#xff0c;完成该课程后&#xff0c;学员将达到DevOps的专家级水平&#x…

【JVM调优系列】如何导出堆内存文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【Docker构建MySQL8.0镜像】

Docker构建MySQL8.0镜像 部署流程1. 拉取docker镜像2. 创建数据卷&#xff0c;存放MySQL数据3. 启动MySQL镜像4. 初始化sql放入MySQL镜像5. 执行MySQL脚本6. MySQL镜像打包7. MySQL镜像迁移 部署流程 1. 拉取docker镜像 docker pull mysql:8.0.35拉取成功后就可以看到镜像了&…

windows项目部署

目录 一、jdk安装&配置 配置jdk的环境配置 二、Tomcat安装 三、MySQL的安装 3.1 Navicat Premium 12 测试连接 3.2 外部访问MySQL测试连接 四、部署项目 4.1 修改mysql的用户密码 一、jdk安装&配置 1.1 双击jdk&#xff0c;进行一个傻瓜式安装 1.2 安装成功…

Linux CentOS 7.6安装nginx详细保姆级教程

一、通过wget下载nginx压缩包 1、进入home文件并创建nginx文件夹用来存放nginx压缩包 cd /home //进入home文件夹 mkdir nginx //创建nginx文件夹 cd nginx //进入nginx文件夹2、下载nginx,我这里下载的是Nginx 1.24.0版本&#xff0c;如果要下载新版本可以去官网进行下载:…

vs2022配置OpenCV测试

1&#xff0c;下载Opencv安装包 OpenCV官网下载地址&#xff1a;Releases - OpenCV 大家可以按需选择版本进行下载&#xff0c;官网下载速度还是比较慢的&#xff0c;推荐大家使用迅雷进行下载 下载安装包到自定义文件夹下 双击安装 按以下图示进行安装 2、 添加环境变量 打…

DWM1000 MAC层

DWM1000 MAC层 MAC层 概述 MAC层&#xff0c;即媒体访问控制层&#xff0c;是数据通信协议栈中的一个重要部分&#xff0c;位于链路层的下半部分&#xff0c;紧邻物理层。在OSI模型中&#xff0c;它属于第二层&#xff0c;即数据链路层的一部分。MAC层的主要职责是控制如何在…

【ArcGIS遇上Python】ArcGIS Python批量筛选多个shp中指定字段值的图斑(以土地利用数据为例)

文章目录 一、案例分析二、提取效果二、代码运行效果三、Python代码四、数据及代码下载一、案例分析 以土地利用数据为例,提取多个shp数据中的旱地。 二、提取效果 原始土地利用数据: 属性表: 提取的旱地:(以图层名称+地类名称命名)

python 爬虫 生成markdown文档

本文介绍的案例为使用python爬取网页内容并生成markdown文档&#xff0c;首先需要确定你所需要爬取的框架结构&#xff0c;根据网页写出对应的爬取代码 1.分析总网页的结构 我选用的是redis.net.com/order/xxx.html (如:Redis Setnx 命令_只有在 key 不存在时设置 key 的值。…

SpringMVC 文件上传和下载

文章目录 1、文件下载2、文件上传3. 应用 Spring MVC 提供了简单而强大的文件上传和下载功能。 下面是对两者的简要介绍&#xff1a; 文件上传&#xff1a; 在Spring MVC中进行文件上传的步骤如下&#xff1a; 在表单中设置 enctype“multipart/form-data”&#xff0c;这样…

大模型学习与实践笔记(六)

一、finetune 简介 两种微调模式&#xff1a;增量预训练 与指令跟随 1.增量预训练 2.指令微调 二、LoRA 与 QLoRA 介绍 三、XTuner 介绍 四、低显存玩转LLM的方法

这是一篇优雅的Springboot2.0使用手册

这是一篇优雅的Springboot2.0使用手册 最近再研究springboot的原理&#x1f60b;颇有收获&#xff0c;现在让我分享一下springboot如何使用吧~ 啥是Springboot 和书上理解的不同&#xff0c;我认为Springboot是一个优秀的快速搭建框架&#xff0c;他通过maven继承方式添加依…

最佳实践分享:SQL性能调优

SQL性能调优是一个需要不断探索和实践的过程&#xff0c;旨在确保数据库查询的高效运行。本文将分享一些SQL性能调优的最佳实践&#xff0c;帮助您提升数据库性能&#xff0c;减少查询响应时间。 一、索引优化 索引是提高查询性能的关键。以下是一些关于索引优化的建议&#…

<软考高项备考>《论文专题 - 71 风险管理(3)》

3 过程2-识别风险 3.1 问题 4W1H过程做什么是识别单个项目风险以及整体项目风险的来源&#xff0c;并记录风险特征的过程。作用:1、记录现有的单个项目风险&#xff0c;以及整体项目风险的来源:2、汇总相关信息&#xff0c;以便项目团队能够恰当地应对已识别的风险。为什么做…

数据结构之bool类

bool类 bool 是布尔类。它是最简单的一个类&#xff0c;其取值有两种&#xff0c;1和O&#xff0c;即 True 和 False。可以这样简单地理解&#xff0c;除了1和0以及 True 和 False 的情况之外&#xff0c;但凡有值&#xff08;非空&#xff09;即为真&#xff0c;但凡无值&…

Komodor:Kubernetes 监控工具全面指南

为了方便起见&#xff0c;Komodor 提供了一个简单的 Web 界面&#xff0c;以帮助您监控 Kubernetes 集群的状态。它拥有付费和免费增值计划&#xff0c;除了在出现问题时通知用户外&#xff0c;还拥有一系列方便的工具&#xff0c;用于跟踪和管理集群中部署的资源的状态。让我们…

预处理/预编译详解(C/C++)

在上一篇的bolg中的编译与链接中提到过预处理&#xff0c;但只是较为简单的讲解&#xff0c;本篇将会对预处理进行详细的讲解。 其中在预处理中很重要的一个一个知识点是#define定义常量与宏&#xff0c;还区分了宏与函数的区别&#xff0c;以及#和##符号&#xff0c;还涉及条件…

Linux第29步_安装“Notepad++”软件

STM32CubeProgrammer脚本文件的后缀为“.tsv”&#xff0c;ST公司官方也叫做FlashLayout。在烧写“TF-A固件”之前&#xff0c;我们需要用“Notepad”软件打开“后缀为.tsv”的脚本文件&#xff0c;根据需求决定哪些文件需要更新&#xff0c;设置好这个脚本文件。 在后期使用S…