跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • Spring Data JPA
  • MyBatis

    • Mybatis高级使用

      • 自定义持久层框架
      • Mybatis相关概念
      • Mybatis基本应用
      • Mybatis配置文件深入
      • Mybatis复杂映射开发
      • Mybatis注解开发
      • Mybatis缓存
      • Mybatis插件
      • Mybatis架构原理
      • Mybatis源码剖析
      • 设计模式
        • 例子:使用构建者设计模式来生产computer
          • 1. 定义computer
          • ComputerBuilder
          • 调用
        • Mybatis中的体现
        • 下面我们来实现简单工厂模式:
          • 1. 创建抽象产品类
          • 2. 创建具体产品类
          • 3. 创建工厂类
          • 客户端调用工厂类
        • Mybatis 体现:
        • Mybatis中实现:
    • Mybatis源码剖析

    • Mybatis-Plus
    • 资料
  • Spring

  • SpringBoot

  • Jdk

  • Tomcat

  • Netty

  • 若依

  • Traefik

  • Openresty

  • 开源框架
  • MyBatis
  • Mybatis高级使用
Revin
2023-07-23
目录

设计模式

虽然我们都知道有3类23种设计模式,但是大多停留在概念层面,Mybatis源码中使用了大量的设计模式,观察设计模式在其中的应用,能够更深入的理解设计模式

Mybati s至少用到了以下的设计模式的使用:

模式 mybatis 体现
Builder模式 例如SqlSessionFactoryBuilder、Environment;
工厂方法模式 例如SqlSessionFactory、TransactionFactory、LogFactory
单例模式 例如 ErrorContext 和 LogFactory;
代理模式 Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理还有executor.loader包使用了 cglib或者javassist达到延迟加载的效果
组合模式 例如SqlNode和各个子类ChooseSqlNode等;
模板方法模式 例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler;
适配器模式 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
装饰者模式 例如Cache包中的cache.decorators子包中等各个装饰者的实现;
迭代器模式 例如迭代器模式PropertyTokenizer;

接下来对Builder构建者模式、工厂模式、代理模式进行解读,先介绍模式自身的知识,然后解读在

Mybatis中怎样应用了该模式。

# 11.1 Builder构建者模式

Builder模式的定义是"将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚至只会构建产品的一个部分,直白来说,就是使用多个简单的对象一步一步构建成一个复杂的对象

# 例子:使用构建者设计模式来生产computer

主要步骤:

1、将需要构建的目标类分成多个部件(电脑可以分为主机、显示器、键盘、音箱等部件);

2、 创建构建类;

3、 依次创建部件;

4、 将部件组装成目标对象

# 1. 定义computer

package com.lagou.dao;


import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.session.SqlSession;
import java.util.Optional;


public class Computer {
   private String displayer;
   private String mainUnit;
   private String mouse;
   private String keyboard;


   public String getDisplayer() {
       return displayer;
    }
    
   public void setDisplayer(String displayer) {
       this.displayer = displayer; 
    }
    
   public String getMainUnit() {
       return mainUnit;
   }
  
   public void setMainUnit(String mainUnit) {
       this.mainUnit = mainUnit;
    }
    
   public String getMouse() {
       return mouse;
    }
    
   public void setMouse(String mouse) {
       this.mouse = mouse;
   }
  
   public String getKeyboard() {
       return keyboard;
   }
  
   public void setKeyboard(String keyboard) {
       this.keyboard = keyboard;
 
  }
  
   @Override
   public String toString() {
       return "Computer{" + "displayer='" + displayer + '\'' + ", mainUnit='" + mainUnit + '\'' + ", mouse='" + mouse + '\'' + ", keyboard='" + keyboard + '\'' + '}';
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# ComputerBuilder

public static class ComputerBuilder {
   private ComputerBuilder target = new ComputerBuilder();
   public Builder installDisplayer(String displayer) {
       target.setDisplayer(displayer);
       return this;
 
}
   public Builder installMainUnit(String mainUnit) {
       target.setMainUnit(mainUnit);
       return this;
 
}
   public Builder installMouse(String mouse) {
       target.setMouse(mouse);
       return this;
 
}
   public Builder installKeybord(String keyboard) {
       target.setKeyboard(keyboard);
       return this;
 
}
   public ComputerBuilder build() {
       return target;
 
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 调用

public static void main(String[]args){
       ComputerBuilder computerBuilder=new ComputerBuilder();
       computerBuilder.installDisplayer("显万器");
       computerBuilder.installMainUnit("主机");
       computerBuilder.installKeybord("键盘");
       computerBuilder.installMouse("鼠标");
       Computer computer=computerBuilder.Builder();
       System.out.println(computer);
}
1
2
3
4
5
6
7
8
9

# Mybatis中的体现

SqlSessionFactory 的构建过程:

Mybatis的初始化工作非常复杂,不是只用一个构造函数就能搞定的。所以使用了建造者模式,使用了大 量的Builder,进行分层构造,核心对象Configuration使用了 XmlConfigBuilder来进行构造

Mybatis_Page98_001

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml 和所有的 *Mapper.xml 文件,构建 Mybatis 运行的核心对象 Configuration对 象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

private void parseConfiguration(XNode root) {
try {
    //issue #117 read properties first
    //解析<properties />标签
    propertiesElement(root.evalNode("properties"));
    // 解析 <settings /> 标签
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //加载自定义的VFS实现类
    loadCustomVfs(settings);
    // 解析 <typeAliases /> 标签
    typeAliasesElement(root.evalNode("typeAliases"));
    //解析<plugins />标签
    pluginElement(root.evalNode("plugins"));
    // 解析 <objectFactory /> 标签
    objectFactoryElement(root.evaINode("obj ectFactory"));
    // 解析 <objectWrapper Factory /> 标签
    obj ectWrappe rFacto ryElement(root.evalNode("objectWrapperFactory"));
    // 解析 <reflectorFactory /> 标签
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // 赋值 <settings /> 到 Configuration 属性
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 解析 <environments /> 标签
    environmentsElement(root.evalNode("environments"));
    // 解析 <databaseIdProvider /> 标签
    databaseldProviderElement(root.evalNode("databaseldProvider"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

其中 XMLConfigBuilder 在构建 Configuration 对象时,也会调用 XMLMapperBuilder 用于读取

*Mapper 文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。

//解析<mappers />标签
mapperElement(root.evalNode("mappers"));
1
2

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了 Builder模式来解决

Mybatis_Page99_001

SqlSessionFactoryBuilder类根据不同的输入参数来构建SqlSessionFactory这个工厂对象

# 11.2 工厂模式

在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建型模式。

在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类

例子:生产电脑

假设有一个电脑的代工生产商,它目前已经可以代工生产联想电脑了,随着业务的拓展,这个代工生产商还要生产惠普的电脑,我们就需要用一个单独的类来专门生产电脑,这就用到了简单工厂模式。

# 下面我们来实现简单工厂模式:

# 1. 创建抽象产品类

我们创建一个电脑的抽象产品类,他有一个抽象方法用于启动电脑:

  public abstract class Computer {
  /**
  *产品的抽象方法,由具体的产品类去实现
  */
  public abstract void start();
}
1
2
3
4
5
6

# 2. 创建具体产品类

接着我们创建各个品牌的电脑,他们都继承了他们的父类Computer,并实现了父类的start方法:

public class LenovoComputer extends Computer{
  @Override
  public void start() {
    System.out.println("联想电脑启动");
}
1
2
3
4
5
public class HpComputer extends Computer{
  @Override
  public void start() {
    System.out.println("惠普电脑启动");
  }
}
1
2
3
4
5
6

# 3. 创建工厂类

接下来创建一个工厂类,它提供了一个静态方法createComputer用来生产电脑。你只需要传入你

想生 产的电脑的品牌,它就会实例化相应品牌的电脑对象

import org.junit.runner.Computer;
public class ComputerFactory {
   public static Computer createComputer(String type){
       Computer mComputer=null;
       switch (type) {
           case "lenovo":
               mComputer=new LenovoComputer();
               break;
           case "hp":
             mComputer=new HpComputer();
               break;
     
      }
       return mComputer;
 
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 客户端调用工厂类

客户端调用工厂类,传入“hp”生产出惠普电脑并调用该电脑对象的start方法:

public class CreatComputer {
  public static void main(String[]args){
    ComputerFactory.createComputer("hp").start();
  }
}
1
2
3
4
5

# Mybatis 体现:

Mybatis中执行Sql语句、获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模式。

有一个 SqlSessionFactory 来负责 SqlSession 的创建

Mybatis_Page101_001

SqlSessionFactory

可以看到,该Factory的openSession ()方法重载了很多个,分别支

持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。

在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:

private SqlSession openSessionFromDataSource(ExecutorType execType,
       TransactionIsolationLevel level,boolean autoCommit){
       Transaction tx=null;
       try{
          final Environment environment=configuration.getEnvironment();
          final TransactionFactory transactionFactory= getTransactionFactoryFromEnvironment(environment);
     
 tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit);
     
//根据参数创建制定类型的Executor
final Executor executor=configuration.newExecutor(tx,execType);
//返回的是 DefaultSqlSession
       return new DefaultSqlSession(configuration,executor,autoCommit);
     
}catch(Exception e){
       closeTransaction(tx); // may have fetched a connection so lets call
       close()
       throw ExceptionFactory.wrapException("Error opening session. Cause: "+ e,e);
     
}finally{
       ErrorContext.instance().reset();
      }
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory 获得一个 Transaction 对象,然后通过 Transaction 获取一个 Executor 对象,最后通过configuration、Executor、是否autoCommit三个参数构建了 SqlSession

# 11.3 代理模式

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy,它是一种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代理

举例:

创建一个抽象类,Person接口,使其拥有一个没有返回值的doSomething方法。

/**
*
抽象类人
*/
public interface Person {
  void doSomething();
}
1
2
3
4
5
6
7

创建一个名为Bob的Person接口的实现类,使其实现doSomething方法

/**
*
创建一个名为Bob的人的实现类
*/
public class Bob implements Person {
   public void doSomething() {
       System.out.println("Bob doing something!");
  }
}
1
2
3
4
5
6
7
8
9

(3)创建JDK动态代理类,使其实现InvocationHandler接口。拥有一个名为target的变量,并创建

getTa rget获取代理对象方法

/**
*
JDK动态代理
*
需实现 InvocationHandler 接口 */
public class JDKDynamicProxy implements InvocationHandler {
   //被代理的对象
   Person target;
   // JDKDynamicProxy 构造函数
   public JDKDynamicProxy(Person person) { this.target = person;
 
}
   //获取代理对象
   public Person getTarget() { return (Person)
           Proxy.newProxylnstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
 
}
   //动态代理invoke方法
   public Person invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被代理方法前执行
       System.out.println("JDKDynamicProxy do something before!");
//执行被代理的方法
       Person result = (Person) method.invoke(target, args);
//被代理方法后执行
       System.out.println("JDKDynamicProxy do something after!"); return result;
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

创建JDK动态代理测试类J DKDynamicTest

/**
* JDK动态代理测试
*/
public class JDKDynamicTest {
  public static void main(String[] args) {
    System.out.println("不使用代理类,调用doSomething方法。");
    //不使用代理类
    Person person = new Bob();
    // 调用 doSomething 方法
    person.doSomething();
    System.out.println("分割线-----------");
    System.out.println("使用代理类,调用doSomething方法。");
    //获取代理类
    Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget();
    // 调用 doSomething 方法 proxyPerson.doSomething();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Mybatis中实现:

代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybati s后台帮我们完成具体SQL的执行。

当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用 mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

public class MapperProxyFactory<T> {
   private final Class<T> mapperInterface;
   private final Map<Method, MapperMethod> methodCache = new
           ConcurrentHashMap<Method, MapperMethod>();
   public MapperProxyFactory(Class<T> mapperInterface) {
       this.mapperInterface = mapperInterface;
 
}
   public Class<T> getMapperInterface() {
       return mapperInterface;
 
}
   public Map<Method, MapperMethod> getMethodCache() {
       return methodCache;
       @SuppressWarnings("unchecked")
       protected T newInstance(MapperProxy<T> mapperProxy) {
           return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface }, mapperProxy);
     
}
       public T newInstance(SqlSession sqlSession) {
           final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
           return newInstance(mapperProxy);
     
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用TnewInstance(MapperProxy mapperProxy)生成代理对象然后返回。而查看MapperProxy的代码,可以看到如下内容:

public class MapperProxy<T> implements InvocationHandler, Serializable {
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws  Throwable {
       try {
           if (Object.class.equals(method.getDeclaringClass())) {
               return method.invoke(this, args);
         
} else if (isDefaultMethod(method)) {
               return invokeDefaultMethod(proxy, method, args);
         
     
  }
} catch (Throwable t) {
           throw ExceptionUtil.unwrapThrowable(t);
     
}
       final MapperMethod mapperMethod = cachedMapperMethod(method);
       return mapperMethod.execute(sqlSession, args);
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的

sqlSession.cud>executor.execute>prepareStatement 等一系列方法,完成 SQL 的执行和返回

上次更新: 2025/04/03, 11:07:08
Mybatis源码剖析
MyBatis架构原理&主要组件

← Mybatis源码剖析 MyBatis架构原理&主要组件→

最近更新
01
tailwindcss
03-26
02
PaddleSpeech
02-18
03
whisper
02-18
更多文章>
Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式