Mybatis原理 - 标签解析

news2024/10/6 20:41:24

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

现在控制开源框架行为主流的配置方式就是 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()方法注册TypeHandler

                if (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()) {

            // 获取环境id

            String 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和DataSource

                Environment.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";

        }

        // 初始化DatabaseIdProvider

        Properties 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.coloradmin.cn/o/1400377.html

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

相关文章

Python高级列表操作:性能优化、多线程与数据处理全解析

Python高级列表操作&#xff1a;性能优化、多线程与数据处理全解析 引言Python列表的高级特性列表推导式与生成器表达式列表操作的高级技巧列表与函数式编程列表在数据处理中的应用性能优化与内存管理Python列表与多线程/异步编程结语 引言 在现代软件开发中&#xff0c;选择恰…

【JavaEE Spring】SpringBoot 日志

SpringBoot 日志 1. 日志概述2. 日志使用2.1 打印⽇志2.1.1 在程序中得到⽇志对象2.1.2 使⽤⽇志对象打印⽇志 2.2 ⽇志框架介绍2.2.1 ⻔⾯模式(外观模式)2.2.2 SLF4J 框架介绍 2.3 ⽇志格式的说明2.4 ⽇志级别2.4.1 ⽇志级别的分类2.4.2 ⽇志级别的使⽤ 2.5 ⽇志配置2.5.1 配置…

MySQL---单表查询综合练习

创建emp表 CREATE TABLE emp( empno INT(4) NOT NULL COMMENT 员工编号, ename VARCHAR(10) COMMENT 员工名字, job VARCHAR(10) COMMENT 职位, mgr INT(4) COMMENT 上司, hiredate DATE COMMENT 入职时间, sal INT(7) COMMENT 基本工资, comm INT(7) COMMENT 补贴, deptno INT…

C语言练习day8

变种水仙花 变种水仙花_牛客题霸_牛客网 题目&#xff1a; 思路&#xff1a;我们拿到题目的第一步可以先看一看题目给的例子&#xff0c;1461这个数被从中间拆成了两部分&#xff1a;1和461&#xff0c;14和61&#xff0c;146和1&#xff0c;不知道看到这大家有没有觉得很熟…

前端框架学习 Vue (1) 概念,常用指令

Vue是什么 概念: Vue是一个用于 构建用户界面 的 渐进式 框架 1.构建用户界面:基于数据动态渲染页面 2.渐进式:循序渐进的学习(学一点就能用一点) (1)Vue核心包开发 场景:局部模块改造 (2)Vue核心包&Vue插件 工程化开发 场景:整站开发 3.框架:一套完整的项目…

遇到Access violation at address xxx in module ‘LoadDXF.dll‘.的解决方法

今天在设计PCB的时候&#xff0c;需要导入一个AutoCAD生成的DWG文件&#xff0c;结果导入出错&#xff0c;之前从来没有遇到过。也不清楚原因。错误的内容&#xff0c;如标题所示&#xff1a;Access violation at address xxx in module LoadDXF.dll. 对于我们既搞编程又设计…

PaddleDetection学习1——使用Paddle-Lite在 Android 上实现实时的目标检测功能

在 Android 上使用Paddle-Lite实现实时的目标检测功能 1 环境准备1.1 安装Android Studio1.1.1 安装JAVA JDK1.1.2 Android Studio 安装步骤1.1.3 Android Studio 配置NDK 1.2 Android 手机 2 部署步骤2.1 下载Paddle-Lite-Demo2.2 打开 yolo_detection_demo项目2.2.1 修改buil…

【Spring 篇】MyBatis中的CRUD魔法:数据之美的四重奏

MyBatis&#xff0c;这个数据持久化的魔法师&#xff0c;以其优雅的SQL映射和简洁的配置文件&#xff0c;为我们呈现出一场CRUD&#xff08;Create, Read, Update, Delete&#xff09;的奇妙之旅。在这篇博客中&#xff0c;我们将深入探讨MyBatis中的增、删、改、查操作&#x…

一键搭建你的知识库

效果 说明 由于安装包安装需要glibc>2.7 我就不尝试了 因为glib升级是一个繁琐的过程 没有升级的意义 只是为了体验知识库 没必要浪费时间 1.1docker compose部署trilium 1.1.创建目录 mkdir -p /opt/triliumcd /opt/trilium 1.2.编写docker-comppose.yml文件 vim dock…

非线性失真放大电路设计

文章目录 一、设置要求二、系统组成三、仿真设计3.1 放大电路总体设计3.2 仿真结果3.2.1 正常波形3.2.2 双向失真3.2.3 顶部失真(截至失真)3.2.4 底部失真(饱和失真)3.2.5 交越失真3.2.6 50Khz\2mv放大 四、原理图与PCB设计4.1 放大电路部分原理图4.2 控制电路部分4.3 PCB设计 …

EasyRecovery2024专业免费的数据恢复软件,支持从硬盘、光盘、U盘、移动硬盘、等所有类型的介质上恢复数据。

Ontrack EasyRecovery Home是一款企业级的数据恢复软件&#xff0c;支持从硬盘、光盘、U盘、移动硬盘、硬件RAID及软件RAID等所有类型的介质上恢复数据。支持恢复误删除、磁盘格式化、磁盘重新分区、磁盘逻辑坏道等原因而丢失的数据。支持RAID重建&#xff01;Ontrack EasyReco…

如何正确地为Python项目安装依赖

a、创建Python项目&#xff0c;其结构如下&#xff1a; b、激活虚拟环境 启动DOS窗口—>进入“Scripts”目录&#xff0c;这里为D:\workspace\prj_python_1\venv\Scripts—>执行activate激活虚拟环境&#xff0c;如下所示&#xff1a; Microsoft Windows [版本 10.0.18…

李宏毅 Generative Adversarial Network(GAN)生成对抗网络

(延申)GAN Lecture 1 (2018)- Introduction_哔哩哔哩_bilibili Basic Idea of GAN 附课程提到的各式各样的GAN&#xff1a;https://github.com/hindupuravinash/the-gan-zoo 想要让机器做到的是生成东西。->训练出来一个generator。 假设要做图像生成&#xff0c;要做的是…

【RT-DETR有效改进】华为 | Ghostnetv1一种专为移动端设计的特征提取网络

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

Day33 122买卖股票最佳时机 55跳跃游戏 45跳跃游戏II

122 买卖股票最佳时机 给定一个数组&#xff0c;它的第 i 个元素是一支给定股票第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易&#xff08;多次买卖一支股票&#xff09;。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你…

[计算机网络]基本概念

目录 1.ip地址和端口号 1.1IP地址 1.2端口号 2.认识协议 2.1概念&#xff1a; 2.2知名协议的默认端口 3.五元组 4.协议分层 4.1分层的作用 4.2OSI七层模型 4.3TCP/IP五层&#xff08;四层&#xff09;模型 ​编辑4.4网络设备对应的分层&#xff1a; ​编辑以下为跨…

微软使其AI驱动的阅读导师免费

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Pytest 测试框架与Allure 测试报告——Allure2测试报告-L1

目录&#xff1a; allure2安装 Allure2介绍Allure2报告展示Allure2报告展示-首页概览Allure2报告展示-用例详情页Allure2安装Allure2下载与安装Allure环境验证插件安装-Python插件安装-Java验证插件安装-Javaallure2运行方式 生成测试报告流程使用Allure2运行方式-Python使用A…

如何本地部署虚拟数字克隆人 SadTalker

环境&#xff1a; Win10 SadTalker 问题描述&#xff1a; 如何本地部署虚拟数字克隆人 SadTalker 解决方案&#xff1a; SadTalker&#xff1a;学习逼真的3D运动系数&#xff0c;用于风格化的音频驱动的单图像说话人脸动画 单张人像图像&#x1f64e; ♂️音频&#x1f3…

CSS:backdrop-filter实现毛玻璃的效果

实现效果 实现代码 /* 关键属性 */ background-color: rgba(255, 255, 255, 0.4); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);完整代码 <style>/* 遮罩层 */.mo-mask {position: fixed;top: 0;bottom: 0;left: 0;right: 0;width: 100%;height…