MyBatis3源码深度解析(十四)SqlSession的创建与执行(一)Configuration与SqlSession的创建过程

文章目录

  • 第五章 SqlSssion的创建过程
    • 前言
    • 5.1 XPath方法解析XML文件
      • 5.1.1 XPath的基本用法
      • 5.1.2 MyBatis使用XPathParser工具类
    • 5.2 Configuration实例创建过程
    • 5.3 SqlSession实例创建过程

第五章 SqlSssion的创建过程

前言

MyBatis的核心组件之一SqlSession对象,表示框架与数据库建立的会话,通过该对象的实例可以完成对数据库的增删改查操作。

SqlSession对象的创建过程可以拆解为3个阶段:Configuration实例的创建过程、SqlSessionFactory实例的创建过程和SqlSession实例化的过程。

5.1 XPath方法解析XML文件

MyBatis的主配置文件和Mapper配置文件都是使用的是XML格式。

MyBatis框架在启动时,会解析这些XML配置,将配置信息转换并注册到Configuration组件中。

JDK API提供了3种方式解析XML,分别为DOM、SAX和XPath。这3种方式各有特点,MyBatis选用的方式是XPath

因此,在研究Configuration组件的创建过程之前,有必要研究一下如何通过XPath解析XML文件。

5.1.1 XPath的基本用法

首先创建一个XML文件users.xml:

<?xml version="1.0" encoding="UTF-8"?><users><user id="1"><name>孙悟空</name><age>1500</age><phone>18955245635</phone><birthday>0000-01-01</birthday></user><user id="2"><name>猪八戒</name><age>1000</age><phone>14577898652</phone><birthday>0600-10-01</birthday></user>
</users>

该XML文件中定义的用户信息与Java实体类User的属性是一一对应的:

public class User {private Integer id;private String name;private Integer age;private String phone;private Date birthday;// constructor getter setter ...
}

然后编写测试代码:

示例1@Test
public void testXPath() throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, ParseException {// (1)创建表示XML文档的Document对象DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();InputStream inputStream = Resources.getResourceAsStream("users.xml");Document document = builder.parse(inputStream);// (2)创建用于执行XPath表达式的XPath对象XPath xPath = XPathFactory.newInstance().newXPath();// (3)使用XPath对象执行表达式,获取XML内容NodeList nodeList = (NodeList) xPath.evaluate("/users/*", document, XPathConstants.NODESET);List<User> userList = new ArrayList<>();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");for (int i = 1; i < nodeList.getLength() + 1; i++) {String path = "/users/user[" + i + "]";String idStr = (String) xPath.evaluate(path + "/@id", document, XPathConstants.STRING);String name = (String) xPath.evaluate(path + "/name", document, XPathConstants.STRING);String ageStr = (String) xPath.evaluate(path + "/age", document, XPathConstants.STRING);String phone = (String) xPath.evaluate(path + "/phone", document, XPathConstants.STRING);String birthdayStr = (String) xPath.evaluate(path + "/birthday", document, XPathConstants.STRING);User user = new User(Integer.valueOf(idStr), name, Integer.valueOf(ageStr), phone, sdf.parse(birthdayStr));userList.add(user);}userList.forEach(System.out::println);
}

控制台打印执行结果:

User{id=1, name='孙悟空', age=1500, phone='18955245635', birthday=Thu Jan 01 00:00:00 CST 1}
User{id=2, name='猪八戒', age=1000, phone='14577898652', birthday=Sat Oct 01 00:00:00 CST 600}

由 示例1 可知,使用JDK提供的XPath解析XML文件的过程大致如下:

(1)创建表示XML文档的Document对象

无论通过哪种方式解析XML文件,都需要先创建表示XML文档的Document对象。

Document对象的创建依赖于DocumentBuilder对象,而DocumentBuilder对象采用工厂模式创建,因此首先需要调用DocumentBuilderFactory类的newInstance()方法创建一个工厂类,然后调用工厂类的newDocumentBuilder()方法创建DocumentBuilder对象,再调用DocumentBuilder对象的parse()方法创建Document对象。parse()方法需要XML文件的输入流作为参数。

(2)创建用于执行XPath表达式的XPath对象

XPath对象也采用工厂模式创建,因此首先要调用XPathFactory类的newInstance()方法创建一个工厂类,然后调用工厂类的newXPath()方法创建一个XPath对象。

(3)使用XPath对象执行表达式,获取XML内容

XPath表达式是一种在XML文档中用于定位节点的语言。它基于XML的树状结构,允许通过路径表达式来选取节点。XPath表达式可以组合使用,以实现更复杂的查找和选择。

XPath表达式的基本结构如下:
1. 节点选择。使用“/”符号从根节点开始选择,例如“/catalog/cd/price”会选择文档中根节点下的“catalog”子节点下的“cd”子节点下的“price”元素。(简单讲,就是一路往下找)
2. 相对和绝对路径。使用“//”符号选择文档中在任何位置匹配上的节点,例如“//book[@id=‘chinese’]”会选择文档中所有ID为“chinese”的“book”元素。
3. 选取当前节点。使用“.”符号选择当前操作的节点,例如“./childnode”会选择当前节点下的“childnode”子节点。
4. 选取父节点。使用“…”符号选择当前节点的父节点,例如“…/childnode”会选择当前节点的父节点下的“childnode”子节点。
5. 选取属性。使用“@”符号选择节点的属性,例如“@id”会选择元素标签上的ID属性。
6. 选取文本。使用“text()”函数选择节点的文本内容,例如“text()=‘chinese’”会选择所有文本内容为“chinese”的节点。
7. 谓语。放在方括号中的谓语用来筛选节点,例如“//book[price>35]”会选择所有价格大于35的“book”元素。
8. 通配符。使用“”符号选择所有匹配的节点,例如“//[@id]”会选择所有ID属性不为空的节点。
9. 命名空间。使用“@”符号和命名空间前缀来指定节点的命名空间,例如“@xmlns:a=‘http://www.example.com/a’”会选择所有命名空间前缀为“a”的节点。

在 示例1 中,首先执行的XPath表达式是"/users/*",它的执行结果是一个NodeList对象,表示“users”节点下的所有节点,即2个“user”节点,因此for循环中nodeList.getLength()的结果为2。

在for循环中,首先执行的XPath表达式是/users/user[i]/@id,表示获取“user”节点的id属性。然后依次执行的XPath表达式是/users/user[i]/name/users/user[i]/age/users/user[i]/phone/users/user[i]/birthday,分别表示获取“user”节点的“name”、“age”、“phone”、“birthday”元素。

需要注意的是,XPath对象的evaluate()方法的最后一个由XPathConstants类指定的参数,用于设置XPath表达式要返回的值的类型。

源码1javax.xml.xpath.XPathConstantspublic class XPathConstants {private XPathConstants() { }public static final QName NUMBER = new QName("http://www.w3.org/1999/XSL/Transform", "NUMBER");public static final QName STRING = new QName("http://www.w3.org/1999/XSL/Transform", "STRING");public static final QName BOOLEAN = new QName("http://www.w3.org/1999/XSL/Transform", "BOOLEAN");public static final QName NODESET = new QName("http://www.w3.org/1999/XSL/Transform", "NODESET");public static final QName NODE = new QName("http://www.w3.org/1999/XSL/Transform", "NODE");public static final String DOM_OBJECT_MODEL = "http://java.sun.com/jaxp/xpath/dom";
}

由 源码1 可知,XPath表达式的执行结果为XML节点对象(Node、NodeLis等)或者字符串、数值类型、布尔类型等。

5.1.2 MyBatis使用XPathParser工具类

MyBatis为了简化XPath的操作,提供了一个XPathParser工具类封装了对XML的解析操作,同时使用XNode类增强了对XML节点的操作。

因此,使用XPathParser工具类,可以将上面的示例代码修改成如下所示:

示例2@Test
public void testXPathParser() throws IOException {InputStream inputStream = Resources.getResourceAsStream("users.xml");// (1)创建XPathParser工具类XPathParser parser = new XPathParser(inputStream);// (2)调用evalNodes()方法获取所有符合表达式的节List<XNode> nodeList = parser.evalNodes("/users/*");for (XNode node : nodeList) {System.out.println("--------- ");// (3)调用getLongAttribute方法获取节点的属性Long id = node.getLongAttribute("id");System.out.println("id = " + id);List<XNode> children = node.getChildren();for (XNode childNode : children) {// (4)调用getName和getStringBody方法获取节点的名称和值String name = childNode.getName();String stringBody = childNode.getStringBody();System.out.println(name + " = " + stringBody);}}
}

由 示例2 可知,使用XPathParser工具类后,解析XML的逻辑变得更加简便。通过调用XPathParser工具类的evalNodes()方法即可获取所有符合表达式的节点,而获取节点的属性只需调用XNode对象的getLongAttribute()方法,获取节点名称调用getName()方法,获取节点值调用getStringBody()方法。

打开XPathParser工具类的构造方法的源码,可以发现它也是先创建DocumentBuilderFactory对象,再创建DocumentBuilder对象,最后调用DocumentBuilder的parse()方法创建一个Document对象。这与 示例1 的写法是一致的。

5.2 Configuration实例创建过程

Configuration组件是MyBatis非常重要的核心组件之一,主要有以下3个作用:

(1)用于描述MyBatis配置信息,包括主配置文件mybatis-config.xml和Mapper配置文件;
(2)作为容器注册MyBatis的其他组件,例如TypeHandler、MappedStatement等;
(3)提供工厂方法,创建ResultSetHandler、StatementHandler、Executor、ParameterHandler等组件。

在【MyBatis3源码深度解析(十二)MyBatis的核心组件(一)Configuration】中编写的示例项目中,有这样一段代码:

示例3// 读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 创建SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();

由 示例3 可知,在SqlSession实例化之前,MyBatis会通过SqlSessionFactory的build()方法解析主配置文件及所有Mapper配置文件,创建一个Configuration对象。

源码2org.apache.ibatis.session.SqlSessionFactoryBuilderpublic SqlSessionFactory build(Reader reader) {return build(reader, null, null);
}public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {// 创建一个XMLConfigBuilder对象XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);// 调用parse()方法创建Configuration实例return build(parser.parse());} // catch finally ...
}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

由 源码2 可知,在build()方法中会创建一个XMLConfigBuilder对象,该类的parse()方法会返回一个Configuration实例。

源码3org.apache.ibatis.builder.xml.XMLConfigBuilderpublic class XMLConfigBuilder extends BaseBuilder {private final XPathParser parser;public Configuration parse() {// 防止parse()方法被同一个实例调用多次if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// 调用XPathParser的evalNode()方法创建表示configuration节点的XNode对象// parseConfiguration()方法对XNode对象进行处理parseConfiguration(parser.evalNode("/configuration"));return configuration;}// ......
}

由 源码3 可知,parse()方法首先调用XPathParser工具类的evalNode()方法获取XML配置文件中<configuration>节点对应的XNode对象,接着调用parseConfiguration()方法通过该XNode对象获取更多配置信息。

源码4org.apache.ibatis.builder.xml.XMLConfigBuilderprivate void parseConfiguration(XNode root) {try {// issue #117 read properties first// 处理<properties>标签propertiesElement(root.evalNode("properties"));// 处理<settings>标签Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfsImpl(settings);loadCustomLogImpl(settings);// 处理<typeAliases>标签typeAliasesElement(root.evalNode("typeAliases"));// 处理<plugins>标签pluginsElement(root.evalNode("plugins"));// 处理<objectFactory>标签objectFactoryElement(root.evalNode("objectFactory"));// 处理<objectWrapperFactory>标签objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 处理<reflectorFactory>标签reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 处理<environments>标签environmentsElement(root.evalNode("environments"));// 处理<databaseIdProvider>标签databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 处理<typeHandlers>标签typeHandlersElement(root.evalNode("typeHandlers"));// 处理<mappers>标签mappersElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

由 源码4 可知,在parseConfiguration()方法中,对于<configuration>标签的子标签,都有一个单独的处理方法,例如propertiesElement()方法处理<properties>标签,settingsAsProperties()方法处理<settings>标签,其它如注释所示。

这些标签具体有什么作用,参考【MyBatis3源码深度解析(十二)MyBatis的核心组件(一)Configuration】。

每个标签的解析细节,可以以处理<mappers>标签为例子来研究。

假设主配置文件mybatis-config.xml中有以下配置:

<mappers><!--方式一:通过指定XML文件的类路径来注册--><mapper resource="mapper/UserMapper.xml"/><!--方式二:通过指定XML文件的完全限定资源定位符来注册--><mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/><!--方式三:通过Mapper接口的类路径来注册--><mapper class="com.star.mybatis.mapper.UserMapper"/><!--方式四:通过Mapper接口所在包路径类注册--><package name="com.star.mybatis.mapper"/>
</mappers>
源码5org.apache.ibatis.builder.xml.XMLConfigBuilderprivate void mappersElement(XNode context) throws Exception {// 传入的参数是<mappers>标签对应的XNode对象if (context == null) {return;}// 遍历<mappers>标签的子标签for (XNode child : context.getChildren()) {if ("package".equals(child.getName())) {// 如果子标签是<package>,则说明要根据包名来扫描(即方式四)// 则将<package>标签的name属性提取出来// addMappers方法会根据包名使用反射机制提取出包下的所有Mapper接口String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {// 如果子标签是<mapper>,则提取<mapper>标签的resource、url、class属性String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {// resource属性不为空,url、class属性属性为空 -> 方式一// 使用XMLMapperBuilder加载Mapper配置文件ErrorContext.instance().resource(resource);try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {// url属性不为空,resource、class属性属性为空 -> 方式二// 使用XMLMapperBuilder加载Mapper配置文件ErrorContext.instance().resource(url);try (InputStream inputStream = Resources.getUrlAsStream(url)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {// class属性不为空,resource、url属性属性为空 -> 方式三// 使用反射机制加载Mapper接口Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {// throw ...}}
}

由 源码5 可知,解析<mappers>标签的mappersElement()针对四种可行的配置方式分别进行了处理,最终将Mapper配置文件或Mapper接口注册到Configuration组件中。其他标签的处理方法的逻辑与这相似,不再赘述。

5.3 SqlSession实例创建过程

由 示例3 可知,SqlSession实例使用工厂模式创建,因此在创建SqlSession实例之前要创建SqlSessionFactory对象。

SqlSessionFactory对象的创建依赖于SqlSessionFactoryBuilder对象,以主配置文件输入流作为参数调用其build()方法。

由 源码2 可知,build()方法中,首先借助XMLConfigBuilder对象对主配置文件进行解析,生成Configuration对象,然后以Configuration对象为参数,调用重载的build()方法。

源码6org.apache.ibatis.session.SqlSessionFactoryBuilderpublic SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

由 源码6 可知,重载的build方法以Configuration对象为参数,创建了一个SqlSessionFactory对象,具体的落地实现类是DefaultSqlSessionFactory。

SqlSessionFactory创建完毕后,调用其openSession()方法即可创建一个SqlSession实例。

源码7org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@Override
public SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,boolean autoCommit) {Transaction tx = null;try {// (1)获取主配置文件中配置的环境信息final Environment environment = configuration.getEnvironment();// (2)创建事务管理器工厂final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// (3)创建事务管理器tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// (4)根据主配置文件中指定的Executor类型创建对应的Executor实例final Executor executor = configuration.newExecutor(tx, execType);// (5)创建DefaultSqlSession实例return new DefaultSqlSession(configuration, executor, autoCommit);} // catch finally ......
}

由 源码7 可知,openSession()方法主要有5个步骤,首先通过Configuration对象获取主配置文件中通过<environment>标签配置的环境信息,然后根据该标签的<transactionManager>子标签配置的事务管理器类型创建对应的事务管理器工厂,由该工厂创建对应的事务管理器。

事务管理器创建完毕后,调用Configuration对象的newExecutor()方法,根据主配置文件中<settings>标签下的<setting name="defaultExecutorType" value="..."/>配置指定的Executor类型创建对应的Executor实例,默认类型是SIMPLE。

最后以Configuration对象和Executor对象为参数,创建一个DefaultSqlSession对象。至此,SqlSession实例创建过程结束。

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

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

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

相关文章

python知识点总结(四)

这里写目录标题 1、Django 中的缓存是怎么用的&#xff1f;2、现有2元、3元、5元共三种面额的货币&#xff0c;如果需要找零99元&#xff0c;一共有多少种找零的方式?3、代码执行结果4、下面的代码执行结果为&#xff1a;5、说一下Python中变量的作用域。6、闭包7、python2与p…

css属性选择器总结

属性选择器 属性选择器在 CSS2 中就已经被引入&#xff0c;并且在 CSS3 及更高版本中仍然被广泛使用&#xff0c;因为它们提供了一种灵活且强大的方式来选择具有特定属性的元素。 [attr]&#xff1a; 是一个属性选择器&#xff0c;用于选择具有指定属性的所有元素&#xff0c;…

发票OCR-国税可进行的发票查验种类-接口文档

发票查验内容包括发票种类名称、发票代码、发票号码、金额、销售方名称、购买方名称等信息。可以在国家税务总局全国增值税发票查验平台上进行查验&#xff0c;也可以进入发票所属省、直辖市税务局官方网站的“我要查询-发票查询”模块进行查验&#xff0c;企业也可以通过发…

二叉树遍历(牛客网)

描述 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以指针方式存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;空格字符代表空树。建立起此二叉树…

LeetCode_29_中等_两数相除

文章目录 1. 题目2. 思路及代码实现详解&#xff08;Python&#xff09;2.1 位运算与二分查找 1. 题目 给你两个整数&#xff0c;被除数 d i v i d e n d dividend dividend 和除数 d i v i s o r divisor divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算…

20240318uniapp怎么引用组件

在script中增加 import index from "/pages/index/index.vue" 把index直接整个作为一个组件引入 然后注册组件 在export default中增加 components: {index:index }, 注册了index组件&#xff0c;内容为import的index 然后就可以在template里使用 <index&…

机器人可反向驱动能力与力控架构

反向驱动性是电机传动系统的机械特性&#xff0c;它描述了运动是否可以轻松反转 。特别是&#xff0c;反向驱动能力取决于两个因素&#xff1a;传动运动效率和整体执行器机械阻抗。反向运动中传动装置的低运动效率意味着所施加的外力的大部分被运动反作用力抵消。然而&#xff…

Ubuntu 搭建gitlab服务器,及使用repo管理

一、GitLab安装与配置 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 1、安装Ubuntu系统&#xff08;这个教程很多&#xff0c;就不展开了&#xff09;。 2、安装gitlab社区版本&#xff0c;有需…

arm32机器的ubuntu1804的源突然不能update了

换成x86的官方源不行: Hit:1 http://archive.canonical.com/ubuntu bionic InRelease Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB] Get:3 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB] Get:4 http://archive.ubuntu.com/ubu…

GAMES101 学习 2

Lecture 7&#xff1a;Shading 1(lllumination,Shading and Graphics Pipeline) Visibility / occlusion 解决可见性和遮挡的问题 可见性&#xff0c;Z-buffering Z-Buffer 深度缓存 Idea&#xff1a; Store current min. z-value for each sample (pixel)Needs an additi…

python学习3:unittest测试框架初学习

python内置测试框架 unittest&#xff08;xUnit家族成员 参考JUnit&#xff09;doctest&#xff1a; 假设被测试目标: def add(a,b):c a breturn c创建一个"test_同名"的文件夹 基本用法 1 创建测试用例 1 定义TestCase的子类 2 定义test_开头的方法 3 在方法…

vue-resource发送请求

导入依赖 终端输入 npm i vue-resource 使用插件 在main.js中应用插件 import Vue from "vue"; import App from "./App.vue" //引入插件 import vueResource from "vue-resource"; //使用插件 Vue.use(vueResource)new Vue({el:#app,render:h…

k8s工作节点主要模块

背景 k8s集群的worker节点作为主要的pod容器的运行节点&#xff0c;其上面有两个非常核心的模块组件&#xff0c;本文就来简单了解下 k8s工作节点主要模块 1.kublet组件&#xff0c;这个组件运行在工作节点上 1.1 它是一个负责这个节点所有pod运行的指挥官的角色&#xff0…

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Line)

直线绘制组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Line(value?: {width?: string | number, height?: string | number}) 从API version 9开始&#xff0c;该接…

Twincat实现电机控制

不仅是控制系统的核心部分&#xff0c;而且能够将任何基于PC的系统转换为一个带有PLC、NC、CNC和机器人实时操作系统的实时控制系统。TwinCAT软件在工业自动化领域具有广泛的应用&#xff0c;特别是在机器人关节电机控制方面!!! 在机器人关节电机控制方面&#xff0c;TwinCAT通…

实验三 前端性能优化-CSS优化

仓库地址&#xff1a;bj-front: 前端性能与工程化 - Gitee.com 利用简写CSS属性和CSS浅选择器&#xff0c;贯彻DRY原则&#xff0c;来完成对页面的CSS的优化过程&#xff0c;通过避免不良实践&#xff0c; 以及使用高性能的CSS选择器、flexbox布局引擎和CSS过渡&#xff0c;提…

vue3 新特性defineOptions和defineModel

一、vue3.3 新特性defineOptions 在Vue3.3之前&#xff0c;组件的默认组件名为.vue单文件组件文件的名字&#xff0c;假如我们想修改组件名&#xff0c;则需要结合Options API进行修改。defineOptions的出现解决了这个问题。 这个宏可以用来直接在 <script setup> 中声明…

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Shape)

绘制组件的父组件&#xff0c;父组件中会描述所有绘制组件均支持的通用属性。 1、绘制组件使用Shape作为父组件&#xff0c;实现类似SVG的效果。 2、绘制组件单独使用&#xff0c;用于在页面上绘制指定的图形。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有…

html5播放flv视频

参考&#xff1a;flv-h265 - npmHTML5 FLV Player. Latest version: 1.7.0, last published: 6 months ago. Start using flv-h265 in your project by running npm i flv-h265. There are no other projects in the npm registry using flv-h265.https://www.npmjs.com/packag…

高效备考2025年AMC8竞赛:吃透2000-2024年600道真题(免费送题)

我们继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的考生来说&#xff0c;吃透AMC8历年真题是备考更加科学、有效的方法之一。 即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么…