MyBatis源码分析の配置文件解析

文章目录

  • 前言
  • 一、SqlSessionFactoryBuilder
    • 1.1、XMLConfigBuilder
    • 1.2、parse
  • 二、mappers标签的解析
    • 2.1、cacheElement
      • 2.1.1、缓存策略
    • 2.2、buildStatementFromContext
      • 2.2.1、sql的解析


前言

  本篇主要介绍MyBatis源码中的配置文件解析部分。MyBatis是对于传统JDBC的封装,屏蔽了传统JDBC与数据库进行交互,组装参数,获取查询结果并自己封装成对象的繁琐过程。
  原生MyBatis首先需要配置mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="jdbc.properties"/><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><mapper resource="mapper/UserMapper.xml"/></mappers>
</configuration>

  并且指定数据源jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

  创建数据库访问层接口:

public interface UserMapper {List<User> selectAll();User selectById(int id);void insert(User user);void update(User user);void delete(int id);
}

  以及对应的xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mybatis.mapper.UserMapper"><cache/><resultMap id="userResultMap" type="com.example.mybatis.entity.User"><id property="id" column="id"/><result property="name" column="name"/><result property="age" column="age"/></resultMap><select id="selectAll" resultMap="userResultMap">SELECT * FROM users</select><select id="selectById" resultMap="userResultMap" parameterType="int">SELECT * FROM users WHERE id = #{id}</select><insert id="insert" parameterType="com.example.mybatis.entity.User">INSERT INTO users (name, age) VALUES (#{name}, #{age})</insert><update id="update" parameterType="com.example.mybatis.entity.User">UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}</update><delete id="delete" parameterType="int">DELETE FROM users WHERE id = #{id}</delete>
</mapper>

  mybatis-config.xml常见的标签:

标签作用
<settings>控制 MyBatis 全局行为(缓存、懒加载、日志等)
<typeAliases>设置类型别名,简化 Mapper XML 中类名书写
<typeHandlers>自定义类型转换器(Java类型 ↔ JDBC类型)
<plugins>注册插件(如分页插件、SQL打印等)
<objectFactory>自定义对象创建逻辑
<environments>配置数据库环境及事务管理
<mappers>注册 Mapper 映射文件或 Mapper 接口

  原生MyBatis的使用,其中读取配置文件并进行解析,主要体现在SqlSessionFactoryBuilderbuild方法中:

public class Main {public static void main(String[] args) throws Exception {//将xml构筑成configuration配置类Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//解析xml,注册成SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);try (SqlSession session = sqlSessionFactory.openSession()) {User user = session.selectOne("com.example.mybatis.mapper.UserMapper.selectById", 1);System.out.println(user);}}
}

一、SqlSessionFactoryBuilder

1.1、XMLConfigBuilder

  在调用SqlSessionFactoryBuilderbuild方法时,首先会去创建一个XMLConfigBuilder,目的是构建一个XML配置文件解析器对象。
在这里插入图片描述  其中的核心代码,这段代码的作用是注册别名,将配置文件中的 “JDBC”、"POOLED"等关键词和实际的类型进行绑定。
在这里插入图片描述

别名实际类用途
"JDBC"JdbcTransactionFactoryJDBC事务管理器(默认事务方式)
"MANAGED"ManagedTransactionFactory受容器管理的事务(如 Spring)
"JNDI"JndiDataSourceFactory从 JNDI 获取数据源
"POOLED"PooledDataSourceFactory数据库连接池(MyBatis 内置)
"UNPOOLED"UnpooledDataSourceFactory不使用连接池的数据源
"PERPETUAL"PerpetualCache永久缓存
"FIFO"FifoCache先进先出缓存
"LRU"LruCache最近最少使用缓存
"SOFT"SoftCache基于 SoftReference 的缓存
"WEAK"WeakCache基于 WeakReference 的缓存
"DB_VENDOR"VendorDatabaseIdProvider根据数据库类型自动切换 SQL
"XML"XMLLanguageDriverMyBatis 默认的 XML SQL 语言驱动器
"RAW"RawLanguageDriver原生 SQL 写法语言驱动器
"SLF4J"Slf4jImpl使用 SLF4J 的日志输出
"COMMONS_LOGGING"JakartaCommonsLoggingImpl使用 Commons Logging 日志
"LOG4J"Log4jImpl使用 Log4j 日志
"LOG4J2"Log4j2Impl使用 Log4j2 日志
"JDK_LOGGING"Jdk14LoggingImpl使用 JDK 内建日志
"STDOUT_LOGGING"StdOutImpl输出日志到控制台
"NO_LOGGING"NoLoggingImpl不输出日志
"CGLIB"CglibProxyFactory使用 CGLIB 动态代理
"JAVASSIST"JavassistProxyFactory使用 Javassist 动态代理

1.2、parse

  真正解析配置文件的是利用上一步构造出的XMLConfigBuilderparse方法,首先会进行判断,如果已经解析过,则抛出异常,不会重复解析:
在这里插入图片描述  否则就将标记设置为true。并且执行parseConfiguration方法,从根节点进行解析:
  每一行都对应了一个 <mybatis-config.xml> 中的标签,逐步填充 Configuration 对象内容:
在这里插入图片描述

/*** 解析 <configuration> 根节点的各个子标签,并将配置信息填充到 Configuration 对象中*/
private void parseConfiguration(XNode root) {try {// 【1】先解析 <properties> 标签(必须最优先解析),以便后续标签中的占位符 ${} 能被正确替换propertiesElement(root.evalNode("properties"));// 【2】解析 <settings> 标签,将其内容转换为 Properties 对象Properties settings = settingsAsProperties(root.evalNode("settings"));// 【3】解析 settings 中的 vfsImpl 属性(如果配置了自定义 VFS 实现类)loadCustomVfs(settings);// 【4】解析 settings 中的 logImpl 属性(设置日志实现类,如 LOG4J、STDOUT_LOGGING 等)loadCustomLogImpl(settings);// 【5】解析 <typeAliases> 标签,注册用户自定义的别名或包扫描别名typeAliasesElement(root.evalNode("typeAliases"));// 【6】解析 <plugins> 标签,注册 MyBatis 插件(如分页插件、SQL 拦截器等)pluginElement(root.evalNode("plugins"));// 【7】解析 <objectFactory> 标签,设置自定义对象工厂(用于实例化结果对象)objectFactoryElement(root.evalNode("objectFactory"));// 【8】解析 <objectWrapperFactory> 标签,自定义对象包装器(封装结果对象属性访问行为)objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 【9】解析 <reflectorFactory> 标签,自定义反射器工厂(高级反射行为控制)reflectorFactoryElement(root.evalNode("reflectorFactory"));// 【10】将 <settings> 中的配置项应用到 Configuration 对象中settingsElement(settings);// 【11】解析 <environments> 标签,注册事务管理器和数据源配置(必须在 objectFactory 之后执行)environmentsElement(root.evalNode("environments"));// 【12】解析 <databaseIdProvider> 标签,支持数据库厂商识别(如区分 MySQL、Oracle)databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 【13】解析 <typeHandlers> 标签,注册自定义类型处理器(TypeHandler)typeHandlerElement(root.evalNode("typeHandlers"));// 【14】解析 <mappers> 标签,加载 Mapper 映射器(包括 XML 和接口方式)mapperElement(root.evalNode("mappers"));} catch (Exception e) {// 如果解析过程中发生异常,则封装为 BuilderException 抛出throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

  当解析完成后,会得到一个configuration对象,其中就包含了配置文件中的各种值。相当于此时的xml配置文件已经转化为了configuration对象。最后还会将其再次包装成SqlSessionFactory,后续会利用SqlSessionFactory进行sql相关逻辑的执行。
在这里插入图片描述  其中最关键的是mappers标签的解析。

二、mappers标签的解析

  mapperElement方法,首先会拿到mappers根标签,然后进行解析。

/*** 解析 <mappers> 标签,支持三种加载方式:package、resource/url、class*/
private void mapperElement(XNode parent) throws Exception {if (parent != null) {// 遍历 <mappers> 下的所有子节点(可能是 <package> 或 <mapper>)for (XNode child : parent.getChildren()) {// 情况1:<package name="com.xxx.mapper"/>,批量注册包下所有 Mapper 接口if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");// 自动扫描指定包下的所有接口,并注册到 Configuration 中configuration.addMappers(mapperPackage);} else {// 情况2~4:单个 <mapper> 节点,通过 resource/url/class 指定加载方式String resource = child.getStringAttribute("resource"); // 从 classpath 中加载 Mapper XMLString url = child.getStringAttribute("url");           // 从网络路径加载 Mapper XMLString mapperClass = child.getStringAttribute("class"); // 直接加载 Mapper 接口类// 情况2:只指定 resource,加载 Mapper XML 文件if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource); // 设置错误上下文信息try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse(); // 解析 Mapper XML,注册语句映射}// 情况3:只指定 url,加载远程 Mapper XML 文件} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try (InputStream inputStream = Resources.getUrlAsStream(url)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse(); // 同样调用解析逻辑}// 情况4:只指定 class,注册 Mapper 接口类(无 XML 时适用)} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface); // 注册接口类到 MapperRegistry// 情况5:配置冲突,三种方式只能选一种,否则抛异常} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}

  案例中对应的是情况2,首先会注册一个mapper解析器,然后调用其parse方法对案例中UserMapper.xml进行解析,在该方法中,首先会进行判断,如果已经进行过解析,则不会重复解析。
在这里插入图片描述  解析的核心方法在于configurationElement,同样是对于xml中的各种标签再次分类解析:
在这里插入图片描述  这里重点看一下cacheElement以及buildStatementFromContext

2.1、cacheElement

  cacheElement和Mybatis的二级缓存有关。简单的说,Mybatis有两级缓存:

  • 一级缓存是SqlSession 级别的,并且默认开启。
  • 二级缓存是Mapper 映射级别,默认不开启,如果需要,应该在某个mapper.xml中使用cache标签开启。

  cacheElement方法正是解析mapper.xml中的cache标签:

/*** 解析 <cache> 标签,构建二级缓存对象并注册到 Configuration 中。*/
private void cacheElement(XNode context) {// 1. 判断 <cache> 标签是否存在if (context != null) {// 2. 解析缓存类型(默认是 PERPETUAL,即 PerpetualCache)String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 3. 解析缓存淘汰策略(默认是 LRU,即最近最少使用)String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);// 4. 缓存刷新间隔(可选):指定自动清空缓存的时间(毫秒)Long flushInterval = context.getLongAttribute("flushInterval");// 5. 缓存大小(可选):最大缓存对象个数Integer size = context.getIntAttribute("size");// 6. 是否为读写缓存(readOnly=false 表示使用序列化;true 表示共享引用)//    readWrite = true 表示开启对象副本,确保线程安全boolean readWrite = !context.getBooleanAttribute("readOnly", false);// 7. 是否阻塞:当缓存正在被其他线程刷新时,是否阻塞等待boolean blocking = context.getBooleanAttribute("blocking", false);// 8. 获取 <cache> 中配置的其他 <property> 子节点Properties props = context.getChildrenAsProperties();// 9. 构建缓存并注册到 Configuration,封装为 MapperBuilderAssistant.useNewCache()builderAssistant.useNewCache(typeClass,          // 缓存类型类(如 PerpetualCache)evictionClass,      // 淘汰策略类(如 LruCache)flushInterval,      // 缓存刷新间隔size,               // 缓存容量readWrite,          // 是否使用读写模式blocking,           // 是否阻塞模式props               // 自定义属性);}
}

  在useNewCache中,最终会调用CacheBuilderbuild方法:
在这里插入图片描述   build方法中运用到了装饰器模式,所有的Cache都实现了一个共同的父类Cache。
  在**cache = newCacheDecoratorInstance(decorator, cache);这一行代码中,传入LruCache和当前的Cache实例(PERPETUAL),将PERPETUAL包装到LRU中:(LruCache的delegate属性,指向的是传入的PerpetualCache实例)
在这里插入图片描述
在这里插入图片描述  然后继续执行到
cache = setStandardDecorators(cache);**这一行代码,会继续进行装饰器的包装:
在这里插入图片描述  setStandardDecorators方法,对于Cache实例层层包装,赋值给各自的delegate属性:
在这里插入图片描述  包装完成的层次:SynchronizedCache线程同步缓存区->LoggingCache统计命中率以及打印日志->SerializedCache序列化->LruCache最少使用->PerpetualCache默认。
在这里插入图片描述

2.1.1、缓存策略

  默认的PerpetualCache,使用的是HashMap进行存储。
在这里插入图片描述
  而LruCache,为了实现最近最少使用的机制,使用了LinkedHashMap的数据结构,并且重写了它的removeEldestEntry方法,关键在于,LinkedHashMap构造时第三个参数为 true 表示按访问顺序排列:
在这里插入图片描述

LruCache cache = new LruCache(new PerpetualCache("myCache"));
cache.setSize(3);cache.put("A", 1);  // A
cache.put("B", 2);  // A B
cache.put("C", 3);  // A B C
cache.get("A");     // B C A (A 被访问过,移到尾部)
cache.put("D", 4);  // C A D(B 被淘汰)

  SynchronizedCache,每个方法上通过加synchronized保证线程安全:
在这里插入图片描述   LoggingCache,会记录日志,以及统计缓存命中次数:
在这里插入图片描述

2.2、buildStatementFromContext

  buildStatementFromContext是用来解析 select、insert、update、delete 标签中sql语句的方法,首先会解析出这些节点,然后进行循环,获取到XMLStatementBuilder后,执行parseStatementNode方法:
在这里插入图片描述  在parseStatementNode方法中有几个关键点,这一段代码会判断当前的标签是否为select,如果是select标签,则不会清除一级缓存(增删改会清除),以及判断是否使用二级缓存(默认 select 使用)
在这里插入图片描述

2.2.1、sql的解析

  真正执行解析sql的是下图中的代码:
在这里插入图片描述  同样地会先去构建一个XMLScriptBuilder,然后调用其parseScriptNode方法进行解析:
在这里插入图片描述  在parseScriptNode方法中,首先会解析 SQL 标签中的所有子标签,然后去进行判断:

  • 包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 DynamicSqlSource(运行时动态拼接 SQL)
  • 不包含动态 SQL(即是否包含 if、choose、${} 等动态节点)构建 RawSqlSource(直接编译成静态 SQL,提升效率)

在这里插入图片描述  MixedSqlNode对象,实现了SqlNode接口,SqlNode是所有动态 SQL节点的统一接口,而MixedSqlNode代表了 一整个 SQL 脚本块,比如select标签中所有内容就会变成一个 MixedSqlNode。

SqlNode 接口

├── MixedSqlNode // 组合节点
├── StaticTextSqlNode // 静态文本节点:普通 SQL 字符串
├── TextSqlNode // 动态文本节点:包含 ${}
├── IfSqlNode // if 标签
├── ChooseSqlNode // choose/when/otherwise
├── ForEachSqlNode // foreach
├── WhereSqlNode // where
├── TrimSqlNode // trim
├── SetSqlNode // set
└── BindSqlNode // bind

  用一个案例说明,假如我在mapper.xml中定义了如下的sql语句:

<select id="findUser" parameterType="map" resultType="User">SELECT * FROM user<where><if test="name != null">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>

  则生成的结构如下:

MixedSqlNode
├── StaticTextSqlNode(“SELECT * FROM user”)
└── WhereSqlNode
└── MixedSqlNode
├── IfSqlNode(test=“name != null”) → TextSqlNode(“AND name = #{name}”)
└── IfSqlNode(test=“age != null”) → TextSqlNode(“AND age = #{age}”)


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

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

相关文章

golang快速上手基础语法

变量 第一种&#xff0c;指定变量类型&#xff0c;声明后若不赋值&#xff0c;使用默认值0 package mainimport "fmt"func main() {var a int //第一种&#xff0c;指定变量类型&#xff0c;声明后若不赋值&#xff0c;使用默认值0。fmt.Printf(" a %d\n"…

Java中的访问修饰符有哪些

在 Java 中&#xff0c;访问修饰符&#xff08;Access Modifiers&#xff09;用于控制类、方法、变量和构造器的访问权限。Java 提供了四种访问修饰符&#xff0c;分别是&#xff1a; publicprotecteddefault&#xff08;包私有&#xff0c;没有显式修饰符&#xff09;private…

【公务员考试】高效备考指南

高效备考指南&#xff1a;从计划制定到心态调整的全面攻略 公务员考试竞争激烈&#xff0c;备考过程既需要科学规划&#xff0c;也需要持之以恒的努力。结合多位高分考生的经验与专业机构的指导&#xff0c;本文整理了一套系统化的备考策略&#xff0c;涵盖目标设定、学习方法…

工程实践:如何使用SU17无人机来实现室内巡检任务

阿木实验室最近发布了科研开发者版本的无人机SU17&#xff0c;该无人机上集成了四目视觉&#xff0c;三维激光雷达&#xff0c;云台吊舱&#xff0c;高算力的机载计算机&#xff0c;是一个非常合适的平台用于室内外巡检场景。同时阿木实验室维护了多个和无人机相关的开源项目。…

强大的CSS变量

在 CSS 中&#xff0c;变量&#xff08;Custom Properties&#xff09; 允许你定义可重用的值&#xff0c;方便在整个样式表中使用和修改。CSS 变量的基本语法如下&#xff1a; 1. 定义 CSS 变量 CSS 变量通常在 :root 伪类中定义&#xff0c;以便它们可用于整个文档&#xf…

蓝桥杯嵌入式赛道复习笔记1(led点亮)

前言 基础的文件创建&#xff0c;参赛资源代码的导入&#xff0c;我就不说了&#xff0c;直接说CubeMX的配置以及代码逻辑思路的书写&#xff0c;在此我也预祝大家人人拿国奖 理论讲解 原理图简介 1.由于存在PC8引脚到PC15引脚存在冲突&#xff0c;那么官方硬件给的解决方案…

Linux进程1.0--task_struct

1.硬件&#xff1a;冯诺依曼体系结构&#xff1a; 单个分析&#xff1a;、 数据流向&#xff1a;数据必须先进入输入设备&#xff0c;再到存储器&#xff0c;然后由存储器给控制器&#xff0c;控制器收到以后进行相应的处理后&#xff0c;再传回存储器&#xff0c;存储器最终传…

本地部署Jina AI Reader:用Docker打造你的智能解析引擎

本地部署Jina AI Reader&#xff1a;用Docker打造你的智能解析引擎 &#x1f31f; 引言&#xff1a;为什么需要本地部署&#xff1f;&#x1f4cc; 场景应用图谱&#x1f527; 部署指南&#xff08;Linux环境&#xff09;1. 环境准备2. Docker部署3. 验证服务状态 &#x1f680…

贪心算法简介(greed)

前言&#xff1a; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每个决策阶段都选择当前最优解的算法策略&#xff0c;通过局部最优的累积来寻求全局最优解。其本质是"短视"策略&#xff0c;不回溯已做选择。 什么是贪心、如何来理解贪心(个人对贪心的…

代码随想录day17 二叉树part05

654.最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。 递归地在最大值 左边 的 子数组前缀上 构建左子树。 递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums …

宇树人形机器人开源模型

1. 下载源码 https://github.com/unitreerobotics/unitree_ros.git2. 启动Gazebo roslaunch h1_description gazebo.launch3. 仿真效果 H1 GO2 B2 Laikago Z1 4. VMware: vmw_ioctl_command error Invalid argument 这个错误通常出现在虚拟机环境中运行需要OpenGL支持的应用…

通过特征值和特征向量实现的图像压缩和特征提取

前文&#xff0c;我们在学习人工智能的线性代数基础的时候&#xff0c;就了解到&#xff0c;矩阵在人工智能中被广泛使用&#xff0c;接下来我们就从大家非常常见的图像开始&#xff0c;深度理解矩阵在人工智能中的应用。有关线性代数基础的文章可以看的我CSDN:人工智能中的线性…

蓝桥杯2023年第十四届省赛真题-整数删除 暴力-->链表+小根堆

题目来自DOTCPP&#xff1a; 思路&#xff1a; ①每次找到数列中的最小值下标&#xff0c;然后用状态数组st标记它&#xff0c;相当与删除它&#xff0c;之后就不会访问它。 ②对最小值下标左边和右边判断一下&#xff0c;看有没有数字&#xff0c;如果有就把最小值加到两边第…

springboot438-基于SpringBoot的数字化教学资源管理系统(源码+数据库+纯前后端分离+部署讲解等)

&#x1f495;&#x1f495;作者&#xff1a; 爱笑学姐 &#x1f495;&#x1f495;个人简介&#xff1a;十年Java&#xff0c;Python美女程序员一枚&#xff0c;精通计算机专业前后端各类框架。 &#x1f495;&#x1f495;各类成品Java毕设 。javaweb&#xff0c;ssm&#xf…

蓝桥杯刷题——第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

一、0握手问题 - 蓝桥云课 算法代码&#xff1a; #include <iostream> using namespace std; int main() {int sum0;for(int i49;i>7;i--)sumi;cout<<sum<<endl;return 0; } 直接暴力&#xff0c;题意很清晰&#xff0c;累加即可。 二、0小球反弹 - 蓝…

跨境卫士跟vps哪个更好用?跨境卫士为卖家提供固定IP环境

跨境卫士是通过为卖家提供固定的环境 i p来隔离本地电脑环境&#xff0c;为卖家创造一个真实独立的物理环境&#xff0c;让买家再任意电脑&#xff0c;任意网络下都能够安全的管理账号。跨境卫士和紫鸟原理一样&#xff0c;是通过为卖家提供固定的环境 i p来隔离本地电脑环境&a…

coding ability 展开第四幕(滑动指针——巩固篇)超详细!!!!

文章目录 前言水果成篮思路 找到字符串中所有字母异位词思路 串联所有单词的子串思路 最小覆盖子串思路 总结 前言 本专栏上一篇博客&#xff0c;带着大家从认识滑动窗口到慢慢熟悉 相信大家对滑动窗口已经有了大概的认识 其实主要就是抓住——一段连续的区间 今天来学习一些滑…

图解AUTOSAR_CP_BSW_General

AUTOSAR BSW通用规范详解 AUTOSAR基础软件模块通用规范与架构解析 目录 1. 概述 1.1. AUTOSAR BSW通用规范简介1.2. 文档目的与范围2. BSW模块文件结构 2.1. 标准文件组织2.2. 命名规范3. BSW模块接口 3.1. 接口类型3.2. 模块API3.3. 配置参数4. BSW通用架构 4.1. 分层架构4.2.…

如何在Futter开发中做性能优化?

目录 1. 避免不必要的Widget重建 问题&#xff1a;频繁调用setState()导致整个Widget树重建。 优化策略&#xff1a; 2. 高效处理长列表 问题&#xff1a;ListView一次性加载所有子项导致内存暴涨。 优化策略&#xff1a; 3. 图片加载优化 问题&#xff1a;加载高分辨率…

组件通信框架ARouter原理剖析

组件通信框架ARouter原理剖析 一、前言 随着Android应用规模的不断扩大&#xff0c;模块化和组件化开发变得越来越重要。ARouter作为一个用于帮助Android应用进行组件化改造的框架&#xff0c;提供了一套完整的路由解决方案。本文将深入分析ARouter的核心原理和实现机制。 二…