Mybatis原理 - 标签解析

很多开源框架之所以能够流行起来,是因为它们解决了领域内的一些通用问题。但在实际使用这些开源框架的时候,我们都是要解决通用问题中的一个特例问题,所以这时我们就需要使用一种方式来控制开源框架的行为,这就是开源框架提供各种各样配置的核心原因之一。

现在控制开源框架行为主流的配置方式就是 XML 配置方式和注解方式,MyBatis 有两方面的 XML 配置,一个是 mybatis-config.xml 配置文件中的整体配置,另一个是 Mapper.xml 配置文件中的 SQL 语句。

在初始化的过程中,MyBatis 会读取 mybatis-config.xml 这个全局配置文件以及所有的 Mapper 映射配置文件,同时还会加载这两个配置文件中指定的类,解析类中的相关注解,最终将解析得到的信息转换成配置对象。完成配置加载之后,MyBatis 就会根据得到的配置对象初始化各个模块。

MyBatis 在加载配置文件、创建配置对象的时候,会使用到经典设计模式中的构造者模式,所以下面我们就来先介绍一下构造者模式的知识点。

构造者模式

构造者模式最核心的思想就是将创建复杂对象的过程与复杂对象本身进行拆分。通俗来讲,**构造者模式是将复杂对象的创建过程分解成了多个简单步骤,在创建复杂对象的时候,只需要了解复杂对象的基本属性即可,而不需要关心复杂对象的内部构造过程。**这样的话,使用方只需要关心这个复杂对象要什么数据,而不再关心内部细节。

构造者模式的类图如下所示:
在这里插入图片描述
从图中,我们可以看到构造者模式的四个核心组件。

  • Product 接口:复杂对象的接口,定义了要创建的目标对象的行为。
  • ProductImpl 类:Product 接口的实现,它真正要创建的复杂对象,其中实现了我们需要的复杂业务逻辑。
  • Builder 接口:定义了构造 Product 对象的每一步行为。
  • BuilderImpl 类:Builder 接口的具体实现,其中具体实现了构造一个 Product 的每一个步骤,例如上图中的 setPart1()、setPart2() 等方法,都是用来构造 ProductImpl 对象的各个部分。在完成整个 Product 对象的构造之后,我们会通过 build() 方法返回这个构造好的 Product 对象。

使用构造者模式一般有两个目的
第一个目的是将使用方与复杂对象的内部细节隔离,从而实现解耦的效果,使用方提供的所有信息,都是由 Builder 这个“中间商”接收的,然后由 Builder 消化这些信息并构造出一个完整可用的 Product 对象。

第二个目的是简化复杂对象的构造过程。在很多场景中,复杂对象可能有很多默认属性,这时我们就可以将这些默认属性封装到 Builder 中,这样就可以简化创建复杂对象所需的信息。

通过构建者模式的类图我们还可以看出,每个 BuilderImpl 实现都是能够独立创建出对应的 ProductImpl 对象,那么在程序需要扩展的时候,我们只需要添加新的 BuilderImpl 和 ProductImpl,就能实现功能的扩展,这完全符合“开放-封闭原则”。

mybatis-config.xml 解析全流程

介绍完构造者模式相关的知识点之后,下面我们正式开始介绍 MyBatis 的初始化过程。

MyBatis 初始化的第一个步骤就是加载和解析 mybatis-config.xml 这个全局配置文件 ,入口是 XMLConfigBuilder 这个 Builder 对象,它由 SqlSessionFactoryBuilder.build() 方法创建。XMLConfigBuilder 会解析 mybatis-config.xml 配置文件得到对应的 Configuration 全局配置对象,然后 SqlSessionFactoryBuilder 会根据得到的 Configuration 全局配置对象创建一个 DefaultSqlSessionFactory 对象返回给上层使用。

这里创建的 XMLConfigBuilder 对象的核心功能就是解析 mybatis-config.xml 配置文件。XMLConfigBuilder 有一部分能力继承自 BaseBuilder 抽象类,具体继承关系如下图所示:

在这里插入图片描述
BaseBuilder 继承关系图

BaseBuilder 抽象类扮演了构造者模式中 Builder 接口的角色,下面我们先来看 BaseBuilder 中各个字段的定义。

  • configuration(Configuration 类型):MyBatis 的初始化过程就是围绕 Configuration 对象展开的,我们可以认为 Configuration 是一个单例对象,MyBatis 初始化解析到的全部配置信息都会记录到 Configuration 对象中。
  • typeAliasRegistry(TypeAliasRegistry 类型):别名注册中心。我们在 mybatis-config.xml 配置文件中,使用 标签为很多类定义了别名。
  • typeHandlerRegistry(TypeHandlerRegistry 类型):TypeHandler 注册中心。除了定义别名之外,我们在 mybatis-config.xml 配置文件中,还可以使用 标签添加自定义 TypeHandler 实现,实现数据库类型与 Java 类型的自定义转换,这些自定义的 TypeHandler 都会记录在这个 TypeHandlerRegistry 对象中

除了关联 Configuration 对象之外,BaseBuilder 还提供了另外两个基本能力:

  • 解析别名,核心逻辑是在 resolveAlias() 方法中实现的,主要依赖于 TypeAliasRegistry 对象;
  • 解析 TypeHandler,核心逻辑是在 resolveTypeHandler() 方法中实现的,主要依赖于 TypeHandlerRegistry 对象。

了解了 BaseBuilder 提供的基础能力之后,我们回到 XMLConfigBuilder 这个 Builder 实现类,看看它是如何解析 mybatis-config.xml 配置文件的。

首先我们来了解一下 XMLConfigBuilder 的核心字段。

  • parsed(boolean 类型):状态标识字段,记录当前 XMLConfigBuilder 对象是否已经成功解析完 mybatis-config.xml 配置文件。
  • parser(XPathParser 类型):XPathParser 对象是一个 XML 解析器,这里的 parser 对象就是用来解析 mybatis-config.xml 配置文件的。
  • environment(String 类型): 标签定义的环境名称。
  • localReflectorFactory(ReflectorFactory 类型):ReflectorFactory 接口的核心功能是实现对 Reflector 对象的创建和缓存。

在 SqlSessionFactoryBuilder.build() 方法中也可以看到,XMLConfigBuilder.parse() 方法触发了 mybatis-config.xml 配置文件的解析,其中的 parseConfiguration() 方法定义了解析 mybatis-config.xml 配置文件的完整流程,核心步骤如下:

  • 解析 <properties> 标签;
  • 解析 <settings> 标签;
  • 处理日志相关组件;
  • 解析 <typeAliases> 标签;
  • 解析 <plugins> 标签;
  • 解析 <objectFactory> 标签;
  • 解析 <objectWrapperFactory> 标签;
  • 解析 <reflectorFactory> 标签
  • 解析 <environments> 标签;
  • 解析 <databaseIdProvider> 标签;
  • 解析 <typeHandlers> 标签;
  • 解析 <mappers> 标签。

从 parseConfiguration()方法中,我们可以清晰地看到 XMLConfigBuilder 对 mybatis-config.xml 配置文件中各类标签的解析方法,下面我们就逐一介绍这些方法的核心实现。

1. 处理<properties>标签

我们可以通过 <properties> 标签定义 KV 信息供 MyBatis 使用,propertiesElement() 方法的核心逻辑就是解析 mybatis-config.xml 配置文件中的 标签。

<properties> 标签中解析出来的 KV 信息会被记录到一个 Properties 对象(也就是 Configuration 全局配置对象的 variables 字段),在后续解析其他标签的时候,MyBatis 会使用这个 Properties 对象中记录的 KV 信息替换匹配的占位符。

2. 处理<settings>标签

MyBatis 中有很多全局性的配置,例如,是否使用二级缓存、是否开启懒加载功能等,这些都是通过 mybatis-config.xml 配置文件中的 标签进行配置的。

XMLConfigBuilder.settingsAsProperties() 方法的核心逻辑就是解析 标签,并将解析得到的配置信息记录到 Configuration 这个全局配置对象的同名属性中,具体实现如下:

private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}// 处理<settings>标签的所有子标签,也就是<setting>标签,将其name属性和value属性// 整理到Properties对象中保存Properties props = context.getChildrenAsProperties();// 创建Configuration对应的MetaClass对象MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);// 检测Configuration对象中是否包含每个配置项的setter方法for (Object key : props.keySet()) {if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");}}return props;}

3. 处理和标签

XMLConfigBuilder 中提供了 typeAliasesElement() 方法和 typeHandlerElement() 方法,分别用来负责处理 标签和 标签,解析得到的别名信息和 TypeHandler 信息就会分别记录到 TypeAliasRegistry 和 TypeHandlerRegistry(前面介绍 BaseHandler 的时候,我们已经简单介绍过这两者了)。

下面我们以 typeHandlerElement() 方法为例来分析一下这个过程:

private void typeHandlerElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) { // 处理全部<typeHandler>子标签if ("package".equals(child.getName())) { // 如果指定了package属性,则扫描指定包中所有的类,// 并解析@MappedTypes注解,完成TypeHandler的注册String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {// 如果没有指定package属性,则尝试获取javaType、jdbcType、handler三个属性String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");String handlerTypeName = child.getStringAttribute("handler");// 根据属性确定TypeHandler类型以及它能够处理的数据库类型和Java类型Class<?> javaTypeClass = resolveClass(javaTypeName);JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class<?> typeHandlerClass = resolveClass(handlerTypeName);// 调用TypeHandlerRegistry.register()方法注册TypeHandlerif (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}}}

4. 处理标签

我们知道 MyBatis 是一个非常易于扩展的持久层框架,而插件就是 MyBatis 提供的一种重要扩展机制。

我们可以自定义一个实现了 Interceptor 接口的插件来扩展 MyBatis 的行为,或是拦截 MyBatis 的一些默认行为。插件的工作机制我们会在后面的课时中详细分析,这里我们重点来看 MyBatis 初始化过程中插件配置的加载,也就是 XMLConfigBuilder 中的 pluginElement()方法,该方法的核心就是解析 标签中配置的自定义插件,具体实现如下:

private void pluginElement(XNode parent) throws Exception {if (parent != null) {// 遍历全部的<plugin>子标签for (XNode child : parent.getChildren()) {// 获取每个<plugin>标签中的interceptor属性String interceptor = child.getStringAttribute("interceptor");// 获取<plugin>标签下的其他配置信息Properties properties = child.getChildrenAsProperties();// 初始化interceptor属性指定的自定义插件Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();// 初始化插件的配置interceptorInstance.setProperties(properties);// 将Interceptor对象添加到Configuration的插件链中保存,等待后续使用configuration.addInterceptor(interceptorInstance);}}}

5. 处理标签

MyBatis 支持自定义 ObjectFactory 实现类和 ObjectWrapperFactory。XMLConfigBuilder 中的 objectFactoryElement() 方法就实现了加载自定义 ObjectFactory 实现类的功能,其核心逻辑就是解析 标签中配置的自定义 ObjectFactory 实现类,并完成相关的实例化操作,相关的代码实现如下:

private void objectFactoryElement(XNode context) throws Exception {if (context != null) {// 获取<objectFactory>标签的type属性String type = context.getStringAttribute("type");// 根据type属性值,初始化自定义的ObjectFactory实现ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();// 初始化ObjectFactory对象的配置Properties properties = context.getChildrenAsProperties();factory.setProperties(properties);// 将ObjectFactory对象记录到Configuration这个全局配置对象中configuration.setObjectFactory(factory);}

除了 标签之外,我们还可以通过 标签和 标签配置自定义的 ObjectWrapperFactory 实现类和 ReflectorFactory 实现类,这两个标签的解析分别对应 objectWrapperFactoryElement() 方法和 reflectorFactoryElement() 方法,两者实现与 objectFactoryElement() 方法实现类似

6. 处理标签

在 MyBatis 中,我们可以通过 标签为不同的环境添加不同的配置,例如,线上环境、预上线环境、测试环境等,每个 标签只会对应一种特定的环境配置。

environmentsElement() 方法中实现了 XMLConfigBuilder 处理 标签的核心逻辑,它会根据 XMLConfigBuilder.environment 字段值,拿到正确的 标签,然后解析这个环境中使用的 TransactionFactory、DataSource 等核心对象,也就知道了 MyBatis 要请求哪个数据库、如何管理事务等信息。

下面是 environmentsElement() 方法的核心逻辑:

private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) { // 未指定使用的环境id,默认获取default值 environment = context.getStringAttribute("default");}// 获取<environment>标签下的所有配置for (XNode child : context.getChildren()) {// 获取环境idString id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) { // 获取<transactionManager>、<dataSource>等标签,并进行解析,其中会根据配置信息初始化相应的TransactionFactory对象和DataSource对象TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));DataSource dataSource = dsFactory.getDataSource();// 创建Environment对象,并关联创建好的TransactionFactory和DataSourceEnvironment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);// 将Environment对象记录到Configuration中,等待后续使用configuration.setEnvironment(environmentBuilder.build());}}}}

7. 处理标签

在 MyBatis 中编写的都是原生的 SQL 语句,而很多数据库产品都会有一些 SQL 方言,这些方言与标准 SQL 不兼容。

在 mybatis-config.xml 配置文件中,我们可以通过 标签定义需要支持的全部数据库的 DatabaseId,在后续编写 Mapper 映射配置文件的时候,就可以为同一个业务场景定义不同的 SQL 语句(带有不同的 DataSourceId),来支持不同的数据库,这里就是靠 DatabaseId 来确定哪个 SQL 语句支持哪个数据库的。

databaseIdProviderElement() 方法是 XMLConfigBuilder 处理 标签的地方,其中的核心就是获取 DatabaseId 值,具体实现如下:

private void databaseIdProviderElement(XNode context) throws Exception {DatabaseIdProvider databaseIdProvider = null;if (context != null) {// 获取type属性值String type = context.getStringAttribute("type");if ("VENDOR".equals(type)) { // 兼容操作type = "DB_VENDOR";}// 初始化DatabaseIdProviderProperties properties = context.getChildrenAsProperties();databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();databaseIdProvider.setProperties(properties);}Environment environment = configuration.getEnvironment();if (environment != null && databaseIdProvider != null) {// 通过DataSource获取DatabaseId,并保存到Configuration中,等待后续使用String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());configuration.setDatabaseId(databaseId);}}

可以看到,解析 标签之后会得到一个 DatabaseIdProvider 对象,其核心方法是 getDatabaseId() 方法,主要是根据前面解析得到的 DataSource 对象来生成 DatabaseId。DatabaseIdProvider 的继承关系如下图所示:

在这里插入图片描述
DatabaseIdProvider 继承关系图

从继承关系图中可以看出,DefaultDatabaseIdProvider 是个空实现,而且已被标记为过时了,所以这里我们就重点来看 VendorDatabaseIdProvider 实现。

在 getDatabaseId() 方法中,VendorDatabaseIdProvider 首先会从 DataSource 中拿到数据库的名称,然后根据 标签配置和 DataSource 返回的数据库名称,确定最终的 DatabaseId 标识,具体实现如下:

public String getDatabaseId(DataSource dataSource) {// 省略边界检查和异常处理return getDatabaseName(dataSource);}private String getDatabaseName(DataSource dataSource) throws SQLException {// 从数据库连接中,获取数据库名称String productName = getDatabaseProductName(dataSource);if (this.properties != null) {// 根据<databaseIdProvider>标签配置,查找自定义数据库名称for (Map.Entry<Object, Object> property : properties.entrySet()) {if (productName.contains((String) property.getKey())) {return (String) property.getValue(); // 返回配置的value}}return null;}return productName;}

8. 处理标签

除了 mybatis-config.xml 这个全局配置文件之外,MyBatis 初始化的时候还会加载 标签下定义的 Mapper 映射文件。 标签中会指定 Mapper.xml 映射文件的位置,通过解析 标签,MyBatis 就能够知道去哪里加载这些 Mapper.xml 文件了。

mapperElement() 方法就是 XMLConfigBuilder 处理 标签的具体实现,其中会初始化 XMLMapperBuilder 对象来加载各个 Mapper.xml 映射文件。同时,还会扫描 Mapper 映射文件相应的 Mapper 接口,处理其中的注解并将 Mapper 接口注册到 MapperRegistry 中。

mapperElement() 方法的具体实现如下:

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) { // 遍历每个子标签if ("package".equals(child.getName())) {// 如果指定了<package>子标签,则会扫描指定包内全部Java类型String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {// 解析<mapper>子标签,这里会获取resource、url、class三个属性,这三个属性互斥String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");// 如果<mapper>子标签指定了resource或是url属性,都会创建XMLMapperBuilder对象,// 然后使用这个XMLMapperBuilder实例解析指定的Mapper.xml配置文件if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {// 如果<mapper>子标签指定了class属性,则向MapperRegistry注册class属性指定的Mapper接口Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

小结

重点介绍了 MyBatis 初始化过程中对 mybatis-config.xml 全局配置文件的解析,深入分析了 mybatis-config.xml 配置文件中所有标签的解析流程,让你进一步了解这些配置加载的原理。同时,我们还介绍了构造者模式这一经典设计模式,它是整个 MyBatis 初始化逻辑的基础思想。

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

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

相关文章

小程序样例2:简单图片分类查看

基本功能&#xff1a; 1、根据分类展示图片&#xff0c;点击类目切换图片&#xff1a; 2、点击分类编辑&#xff0c;编辑分类显示&#xff1a; 3、点击某个分类&#xff0c;控制主页该分类显示和不显示&#xff1a; 类目2置灰后&#xff0c;主页不再显示 4、点击分类跳转到具…

Vue记录

vue2、vue3记录 vue2记录 经典vue2结构 index.vue&#xff1a; <template><div>...</div> </template><script>import method from "xxx.js"import component from "xxx.vue"export default {name: "ComponentName&…

typing python 类型标注学习笔记

在Python 3.5版本后引入的typing模块为Python的静态类型注解提供了支持。这个模块在增强代码可读性和维护性方面提供了帮助。 目录 简介为什么需要 Type hints typing常用类型typing初级语法typing基础语法默认参数及 Optional联合类型 (Union Type)类型别名 (Type Alias)子类型…

Redis 面试题 | 02.精选Redis高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【Linux】第三十一站:管道的一些应用

文章目录 一、我们之前的|(竖划线)管道二、自定义shell三、使用管道实现一个简易的进程池1.详解2.代码3.一个小bug4.最终代码 一、我们之前的|(竖划线)管道 cat test.txt | head -10 | tail -5如上代码所示&#xff0c;是我们之前所用的管道 我们拿下面这个举个例子 当我们用…

【Linux】安装n卡驱动以及可能遇到的问题

文章目录 1.换源以及更新2.安装依赖3. 安装n卡驱动独显与核显切换nvidia-settings消失忘记安装依赖无法进入图形化界面的急救命令行无响应办法 1.换源以及更新 目前&#xff0c;换源完全只需要鼠标点点点就可以完成了&#xff0c;打开应用列表里的Software & Updates&…

Spring DI

目录 什么是依赖注入 属性注入 构造函数注入 Setter 注入 依赖注入的优势 什么是依赖注入 依赖注入是一种设计模式&#xff0c;它通过外部实体&#xff08;通常是容器&#xff09;来注入一个对象的依赖关系&#xff0c;而不是在对象内部创建这些依赖关系。这种方式使得对象…

macOS修改默认时区显示中国时间

默认时区不是中国,显示时间不是中国时间 打开终端 ,删除旧区,并复制新时区到etcreb sudo -rm -rf /etc/localtime sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 重启系统后时间显示为中国时间

SQL语句的执行顺序

查询语句语法&#xff1a; SELECT字段列表 FROM表名字段 WHERE条件列表 GROUP BY分组字段列表 HAVING分组后的条件列表 ORDER BY排序字段列表 LIMIT分页参数 执行顺序 #先找到表格 FROM表名字段 WHERE条件列表 GROUP BY分组字段列表 HAVING分组后的条件列表 SELECT字段列表 …

4.C语言——数组

数组 1.什么是数组2.一维数组1.数组定义2.数组赋值3.数组的使用4.数组的存储地址 3.二维数组1.数组定义2.数组赋值3.数组的使用4.数组的存储地址 4.数组名5.数组越界 1.什么是数组 数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量 所有的数组都是由…

【网站项目】329网月科技公司门户网站

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

正则表达式初版

一、简介 REGEXP&#xff1a; Regular Expressions&#xff0c;由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通…

什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(二)

什么是技术架构?架构和框架之间的区别是什么?怎样去做好架构设计?(二)。 技术架构是对某一技术问题(需求)解决方案的结构化描述,由构成解决方案的组件结构及之间的交互关系构成。广义上的技术架构是一系列涵盖多类技术问题设计方案的统称,例如部署方案、存储方案、缓存…

Arduino开发实例-INA219 电流传感器驱动

INA219 电流传感器驱动 文章目录 INA219 电流传感器驱动1、INA219 电流传感器介绍2、硬件准备及接线3、代码实现1、INA219 电流传感器介绍 INA219 模块用于同时测量电流和电压。 该模块使用 I2C 通信传输电压和电流数据。 其他特性: 测量精度:1%最大测量电压:26V最大测量电…

Spring:StopWatch

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、输出总耗时 二、输出所有任务的耗时和占比 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、输出总耗时 public void stopWatc…

C++---判断闰年

一.闰年的定义 闰年是指在公历中&#xff0c;年份可以被4整除但不能被100整除的年份&#xff0c;或者可以被400整除的年份。简单来说&#xff0c;闰年是一个比平年多出一天的年份&#xff0c;即2月有29天。闰年的目的是校准公历与地球公转周期的差异&#xff0c;确保时间计算的…

自然语言推断:注意力之注意(Attending)

注意&#xff08;Attending&#xff09; 第一步是将一个文本序列中的词元与另一个序列中的每个词元对齐。假设前提是“我确实需要睡眠”&#xff0c;假设是“我累了”。由于语义上的相似性&#xff0c;我们不妨将假设中的“我”与前提中的“我”对齐&#xff0c;将假设中的“累…

ARM体系架构

1. 计算机组成 交叉开发: 程序的编写 编译 在 PC机上(宿主机) 但 运行在 开发板(目标机) 嵌入式开的的特点: 开发环境的不同: 交叉开发环境 以应用为中心, 围绕实际功能设计 软件和硬件 量体裁衣 1.1 计算机的基本组成部分: 输入设备 输出设备 存储器 运算器 控制器 总…

为什么 HTTPS 协议能保障数据传输的安全性?

HTTP 协议 在谈论 HTTPS 协议之前&#xff0c;先来回顾一下 HTTP 协议的概念。 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议&#xff0c;它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯&#xff0c;目前协议由之前的 RFC 2616 拆…

SpringBoot整合Dubbo和Zookeeper分布式服务框架使用的入门项目实例

文章目录 SpringBoot整合Dubbo和Zookeeper分布式服务框架使用的入门项目实例Dubbo定义其核心部分包含: 工作原理为什么要用dubbo各个节点角色说明&#xff1a;调用关系说明&#xff1a; dubbo为什么需要和zookeeper结合使用&#xff0c;zookeeper在dubbo体系中起到什么作用&…