Mybatis-plus 源码执行全流程解析

news2025/1/10 22:03:03

Mybatis-plus 源码执行全流程解析

废话 少数流程开始:

1、业务入口:userMapper.insertBatch(entityList);

执行到代理类: $Proxy222 (com.sun.proxy) 开始走代理流程,然后到了代理类:

idea 执行流程取出栈信息:

 invoke:89, MybatisMapperProxy (com.baomidou.mybatisplus.core.override)

提前生成sqlSession ,地方如下:

2、代理类:public class MybatisMapperProxy<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 {//核心接口进入,带着生成的sqlSession
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

来到:

// private final Map<Method, MapperMethodInvoker> methodCache; 类似缓存操作
 private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {// 集合的操作方法,通过接口回调返回数据
            return CollectionUtils.computeIfAbsent(methodCache, method, m -> {
                if (m.isDefault()) {
                    try {
                        if (privateLookupInMethod == null) {
                            return new DefaultMethodInvoker(getMethodHandleJava8(method));
                        } else {
                            return new DefaultMethodInvoker(getMethodHandleJava9(method));
                        }
                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                        | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    return new PlainMethodInvoker(new MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }

 进入新的invoke 方法:

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            //private final MybatisMapperMethod mapperMethod 方法名对象
            return mapperMethod.execute(sqlSession, args);//核心操作
        }

3、来到 public class MybatisMapperMethod 类:

// private final MapperMethod.MethodSignature method ;方法的类名
public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {//MapperMethod.SqlCommand command; 对象
            case INSERT: {//新增会走这里,点击进入,合成参数数据,进入
                Object param = method.convertArgsToSqlCommandParam(args);
                //通过方法名、参数,执行并返回执行结果,进入
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // TODO 这里下面改了
                    if (IPage.class.isAssignableFrom(method.getReturnType())) {
                        result = executeForIPage(sqlSession, args);
                        // TODO 这里上面改了
                    } else {
                        result = sqlSession.selectOne(command.getName(), param);
                        if (method.returnsOptional()
                            && (result == null || !method.getReturnType().equals(result.getClass()))) {
                            result = Optional.ofNullable(result);
                        }
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }

4、来到 public class MapperMethod 的内部类MethodSignature

   //private final ParamNameResolver paramNameResolver; 参数分解器
   public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);//进入
    }

5、来到:public class ParamNameResolver 

public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

最后返回:

 下面重点分析:

result = rowCountResult(sqlSession.insert(command.getName(), param));

点击 sqlSession.insert(command.getName(), param) 进入:三个实现类的其中一个

根据上游入口判断进入spring的类:

public class SqlSessionTemplate implements SqlSession, DisposableBean

  6、来到 SqlSessionTemplate

  //private final SqlSession sqlSessionProxy;方法的全路径、执行参数
  @Override
  public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);//进入
  }

三个实现类:

 来到子类 public class DefaultSqlSession implements SqlSession

 7、DefaultSqlSession

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);//进入执行
  }

来到:

 //private final Executor executor;执行器接口
 //private final Configuration configuration; 配置类
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);//创建声明,进入
      return executor.update(ms, wrapCollection(parameter));//正式执行
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();//实例资源清除
    }
  }

 点击configuration.getMappedStatement(statement) 进入

来到 public class MybatisConfiguration extends Configuration 此类为mybatisplus的类,继承的mybatis的配置类Configuration 。

 8、MybatisConfiguration 类

   //此id就是mapper的全路径,在此服务中应该也是唯一的值
    @Override
    public MappedStatement getMappedStatement(String id) {
        return this.getMappedStatement(id, true);//正式获取声明
    }

来到:

   //protected final Map<String, MappedStatement> mappedStatements 
    @Override
    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
            buildAllStatements();//上游传的true,进入
        }
        return mappedStatements.get(id);//在此处返回
    }

点击: buildAllStatements() 进入父类Configuration,组装的细节后面我们会详细分享

protected void buildAllStatements() {
    parsePendingResultMaps();
    if (!incompleteCacheRefs.isEmpty()) {
      synchronized (incompleteCacheRefs) {
        incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
      }
    }
    if (!incompleteStatements.isEmpty()) {
      synchronized (incompleteStatements) {
        incompleteStatements.removeIf(x -> {
          x.parseStatementNode();
          return true;
        });
      }
    }
    if (!incompleteMethods.isEmpty()) {
      synchronized (incompleteMethods) {
        incompleteMethods.removeIf(x -> {
          x.resolve();
          return true;
        });
      }
    }
  }

返回后进入 executor.update(ms, wrapCollection(parameter));进入切面类

public class Plugin implements InvocationHandler
  //切面方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        //private final Interceptor interceptor;拦截器接口
        return interceptor.intercept(new Invocation(target, method, args));//进入拦截器
      }
      return method.invoke(target, args);//进行执行业务
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

点击: 

来到:public class OptimisticLockerInterceptor implements Interceptor mybatis 中的类

@Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        if (SqlCommandType.UPDATE != ms.getSqlCommandType()) {
            return invocation.proceed();
        }
        Object param = args[1];
        if (param instanceof Map) {
            Map map = (Map) param;
            //updateById(et), update(et, wrapper);
            Object et = map.getOrDefault(Constants.ENTITY, null);
            if (et != null) {
                // entity
                String methodId = ms.getId();
                String methodName = methodId.substring(methodId.lastIndexOf(StringPool.DOT) + 1);
                TableInfo tableInfo = TableInfoHelper.getTableInfo(et.getClass());
                if (tableInfo == null || !tableInfo.isWithVersion()) {
                    return invocation.proceed();
                }
                TableFieldInfo fieldInfo = tableInfo.getVersionFieldInfo();
                Field versionField = fieldInfo.getField();
                // 旧的 version 值
                Object originalVersionVal = versionField.get(et);
                if (originalVersionVal == null) {
                    return invocation.proceed();
                }
                String versionColumn = fieldInfo.getColumn();
                // 新的 version 值
                Object updatedVersionVal = this.getUpdatedVersionVal(fieldInfo.getPropertyType(), originalVersionVal);
                if (PARAM_UPDATE_METHOD_NAME.equals(methodName)) {
                    AbstractWrapper<?, ?, ?> aw = (AbstractWrapper<?, ?, ?>) map.getOrDefault(Constants.WRAPPER, null);
                    if (aw == null) {
                        UpdateWrapper<?> uw = new UpdateWrapper<>();
                        uw.eq(versionColumn, originalVersionVal);
                        map.put(Constants.WRAPPER, uw);
                    } else {
                        aw.apply(versionColumn + " = {0}", originalVersionVal);
                    }
                } else {
                    map.put(Constants.MP_OPTLOCK_VERSION_ORIGINAL, originalVersionVal);
                }
                versionField.set(et, updatedVersionVal);
                return invocation.proceed();
            }
        }
        return invocation.proceed();//点击进入
    }

 来到:public class Invocation 

public Object proceed() throws InvocationTargetException, IllegalAccessException {
  return method.invoke(target, args);//进入
}

到了:public final class Method extends Executable

    @CallerSensitive
    @ForceInline // to ensure Reflection.getCallerClass optimization
    @HotSpotIntrinsicCandidate
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz,
                        Modifier.isStatic(modifiers) ? null : obj.getClass(),
                        modifiers);
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);//进入
    }

 进入:

三个实现类

 9、来到 public class MybatisCachingExecutor implements Executor 实现类 mybatisplus包中

    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms);//刷新清除缓存,下次查询此表,会到数据库查,点击
        // private final Executor delegate;执行器执行
        return delegate.update(ms, parameterObject);//核心执行,点击
    }

 来到:

 private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
            tcm.clear(cache);//清除缓存
        }
    }

点击 delegate.update(ms, parameterObject);

10、到了 public abstract class BaseExecutor implements Executor

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();//清除缓存
    return doUpdate(ms, parameter);//正式执行
  }

点击:

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

抽象方法的重写:有mybatis和plus的两组实现

10、 到:public class MybatisSimpleExecutor extends AbstractBaseExecutor

    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);//生成执行的声明,里面很复杂,后期我们很专题讲解
            stmt = prepareStatement(handler, ms.getStatementLog(), false);//拿数据源重点,进入
            return stmt == null ? 0 : handler.update(stmt);//正式执行,点击
        } finally {
            closeStatement(stmt);//关闭声明
        }
    }

点击 prepareStatement(handler, ms.getStatementLog(), false);

private Statement prepareStatement(StatementHandler handler, Log statementLog, boolean isCursor) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);//取数据源,进入
        stmt = handler.prepare(connection, transaction.getTimeout());封装数据源到声明里
        //游标不支持返回null.
        if (stmt == null && !isCursor) {
            return null;
        } else {
            handler.parameterize(stmt);
            return stmt;
        }
    }

11、到了父类  public abstract class BaseExecutor implements Executor

protected Connection getConnection(Log statementLog) throws SQLException {
    // protected Transaction transaction;
    Connection connection = transaction.getConnection();//获取数据源,进入
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

 点击发现常用的三种实现:

显然 进入 mybatis-spring 包里的SpringManagedTransaction

10、来到 public class SpringManagedTransaction implements Transaction 类

 @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();//获取连接,进入
    }
    return this.connection;
  }

点击 openConnection()

private void openConnection() throws SQLException {
    //通过spring的工具类DataSourceUtils 获取 数据库连接connection(JDK包里的),进入
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();//自动提交的获取
    //是否连接、事务的判断
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
        + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
  }

来到:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
		try {
			return doGetConnection(dataSource);//点击
		}
		catch (SQLException ex) {
			throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
		}
		catch (IllegalStateException ex) {
			throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
		}
	}

 点击:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");

		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				logger.debug("Fetching resumed JDBC Connection from DataSource");
				conHolder.setConnection(fetchConnection(dataSource));
			}
			return conHolder.getConnection();
		}
		// Else we either got no holder or an empty thread-bound holder here.

		logger.debug("Fetching JDBC Connection from DataSource");
		Connection con = fetchConnection(dataSource);//重点进入

		if (TransactionSynchronizationManager.isSynchronizationActive()) {
			try {
				// Use same Connection for further JDBC actions within the transaction.
				// Thread-bound object will get removed by synchronization at transaction completion.
				ConnectionHolder holderToUse = conHolder;
				if (holderToUse == null) {
					holderToUse = new ConnectionHolder(con);
				}
				else {
					holderToUse.setConnection(con);
				}
				holderToUse.requested();
				TransactionSynchronizationManager.registerSynchronization(
						new ConnectionSynchronization(holderToUse, dataSource));
				holderToUse.setSynchronizedWithTransaction(true);
				if (holderToUse != conHolder) {
					TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
				}
			}
			catch (RuntimeException ex) {
				// Unexpected exception from external delegation call -> close Connection and rethrow.
				releaseConnection(con, dataSource);
				throw ex;
			}
		}

		return con;
	}

来到:

private static Connection fetchConnection(DataSource dataSource) throws SQLException {
		Connection con = dataSource.getConnection();//点击进入,dataSource 的实现类
		if (con == null) {
			throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
		}
		return con;
	}

 点击:dataSource.getConnection()发现好的实现类,应该进入哪一个啊?? 

根据业务判断:进入NandaoDataSource        

public class NandaoDataSource  extends AbstractTenantDataSource implements DataSource, NandaoDataSourceMXBean, MBeanRegistration, AutoCloseable{
   @Override
    public Connection getConnection() throws SQLException {
        if (provider == null) {
            throw new Exception("provider is null");
        }
        return getConnection(provider.getUserId());//业务封装回去自定义数据源
    }

进入

public Connection getConnection(String userId) throws SQLException {

        for (int retry = 1; retry <= retryTimes; retry++) {
            UserDbInfo dbInfo = userDbInfoInfoService.getDatabaseInfo(userId);
            
                return getDataSource(dbInfo).getConnection();
           }
    }

这是获取数据源的流程,分析到此,下篇我们分析真正执行数据库的流程 handler.update(stmt);,敬请期待!

 

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

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

相关文章

Mybatis增删改查

Mybatis增删改查 1&#xff0c;配置文件实现CRUD 1.1 环境准备 数据库表&#xff08;tb_brand&#xff09;及数据准备 -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand (-- id 主键id int primary key auto_increment,…

string的认识和使用

目录 一、网站上查看string类 1.网站 2.网站上的string类 二、string类的成员函数 1.默认成员函数 &#xff08;1&#xff09;构造函数、拷贝构造函数和析构函数 &#xff08;2&#xff09;赋值运算符重载 &#xff08;3&#xff09;容量操作 &#xff08;4&#xff09…

JavaWeb用户信息查看的登录、增删改查案例

黑马JavaWeb案例整理记录 文章目录案例源码&#xff1a;一、需求二、设计1、技术选型2、数据库设计三、开发步骤四、详细过程1、创建web项目&#xff0c;部署tomcat&#xff0c;导入jar包2、编写首页index.jsp3、编写servlet、servie、daomain、dao五、案例升级实现1、实现内容…

成长任务|花式show爱心代码

❤️立即投稿❤️ &#x1f4cc;活动规则 最近#李峋同款爱心代码#词条上了热搜&#xff0c;作为程序员的你&#xff0c;可不能错过这个给意中人大展身手的时机 ❤️ 使用代码画一颗#爱心#&#xff0c;将你的代码文件通过本页面投稿&#xff0c;审核通过即可参与活动 ❤️ 可使…

速锐得猎奇与猜想:数字化工业互联网能否真的走进未来

前年的8月份&#xff0c;我们独立设计一款燃气报警器&#xff0c;包括设计产品图纸&#xff0c;嵌入式软件、LED显示、核心代码、CAN网络及485架构、云端SAAS框图&#xff0c;应用场景等&#xff0c;在去年的5月份直接进入到了量产&#xff0c;第一批数量是2000套&#xff0c;升…

详细介绍NLP文本摘要

文章目录文本生成文本摘要抽取式文本摘要抽取式文本摘要方法案例分析优点缺点生成式文本摘要指针生成网络文本摘要预训练模型与生成式摘要优点缺点TextRank文本摘要BertSum模型文本摘要文本生成 文本生成&#xff08;Text Generation&#xff09;&#xff1a;接收各种形式的文…

IT行业几大方向(外行人之误解)

有很多同学一说自己是搞计算机的&#xff0c;搞软件的&#xff0c;做IT的&#xff0c;是个程序员。外行人&#xff0c;就往往很惊奇&#xff0c;说我电脑卡怎么处理&#xff0c;怎么盗别人QQ密码&#xff0c;把谁谁网站黑了&#xff0c;甚至我的网络很慢可以帮我提速一下吗等等…

如何设计es的索引库结构

1、确定索引库的名称 建议和使用的数据库的表名相对应 比如&#xff1a;数据库的表名为 那么索引库的名称可以为&#xff1a;item 2、确定索引库需要的字段 1.根据前端界面来判断需要什么字段 例如&#xff1a; 上边这个界面需要的就是&#xff1a; 分类&#xff0c;品牌&…

使用ZPL控制Zebra打印机

ZPL简介: Zebra编程语言 (ZPL)是所有 ZPL 兼容打印机使用的命令语言。可用于绘制文本、形状、条形码和图像等元素&#xff0c;组合这些元素。打印机使用它作为指令来创建打印在标签上的图像。 想要打印一个标签&#xff0c;您可能需要告诉打印机&#xff1a; 产品名称是什么…

Python学习基础笔记三十四——爬虫小例子

一个爬虫小例子&#xff1a; import requestsimport re import jsondef getPage(url):responserequests.get(url)return response.textdef parsePage(s):comre.compile(<div class"item">.*?<div class"pic">.*?<em .*?>(?P<id&…

Nginx的access.log日志分析工具-goaccess

一、安装goaccess wget http://tar.goaccess.io/goaccess-1.3.tar.gz --no-check-certificate #下载tar包 tar -xzvf goaccess-1.3.tar.gz #解压tar包 cd goaccess-1.3/ #进入目录 ./configure --enable-utf8 --enable-geoiplegacy --prefix/opt/goaccess #检测环境&…

宝塔严重未知安全性漏洞(宝塔面板或Nginx异常)

问题简述 论坛上的帖子 https://www.bt.cn/bbs/thread-105054-1-1.html https://www.bt.cn/bbs/thread-105085-1-1.html https://hostloc.com/thread-1111691-1-1.html 数据库莫名被删 https://www.bt.cn/bbs/thread-105067-1-1.html 以下内容来自群友消息&#xff1a; 速报:宝…

大学生餐饮主题网页制作 美食网页设计模板 学生静态网页作业成品 dreamweaver美食甜品蛋糕HTML网站制作

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

以太网 网线分类

1. 双绞线分类 一类线&#xff1a;主要用于传输语音&#xff08;一类标准主要用于八十年代初之前的电话线缆&#xff09;&#xff0c;不同于数据传输。 二类线&#xff1a;传输频率为1MHZ&#xff0c;用于语音传输和最高传输速率4Mbps的数据传输&#xff0c;常见于使用4MBPS规范…

【Android gradle】自定义一个android gradle插件,并发布到私有Artifactory仓库

1. 前言 最近工作部分内容涉及到gradle 插件的编写&#xff0c;在粗浅了解和编码之余来简单梳理下编写一个gradle 插件的相关用法。这里编写的插件还是发布到在前面两篇博客中的Artifactory仓库中。一共分为如下几步&#xff1a; 配置定义gradle插件、上传到 Artifactory仓库…

基于单片机的加热炉炉温控制系统设计

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1课题背景及意义 1 1.2发展现状 2 1.3研究主要内容 3 第2章 加热炉炉温控制系统总体方案设计 4 2.1总体方案的确定 4 2.2加热炉炉温控制系统组成 5 第3章 加热炉炉温控制系统技术和算法介绍 6 3.1 AT89C51简介 6 3.1.1单片机的引脚介…

Crossover2023mac苹果电脑系统上运行Windows程序虚拟机工具模拟器

CrossOver是一款可以让Mac和Linux系统中正常运行Windows软件的应用程序。它不像虚拟机一样需要安装Windows系统之后才可以安装Windows的应用程序&#xff0c;这一方式给大多数用户带来了方便。通过CrossOver实现跨平台的文件复制粘贴&#xff0c;使Mac/Linux系统与Windows应用良…

海康视频回放,rtsp视频接口转换成.m3u8格式文件

通过海康接口返回的rtsp视频接口&#xff0c;转换成.m3u8格式文件&#xff0c;逻辑如下 1、采用ffmpeg实时转化rtsp链接视频&#xff0c;转化为m3u8&#xff0c;存放服务器固定地址 2、采用nginx代理视频成.m3u8视频 3、采用tokenredis方式处理视频播放和删除过程&#xff0…

太好玩了,我用 Python 做了一个 ChatGPT 机器人

大家好&#xff0c;我是早起。 毫无疑问&#xff0c;ChatGPT 已经是当下编程圈最火的话题之一&#xff0c;它不仅能够回答各类问题&#xff0c;甚至还能执行代码&#xff01; 或者是变成一只猫 因为它实在是太好玩&#xff0c;我使用Python将ChatGPT改造&#xff0c;可以实现在…

亚马逊黑五哑火,中国跨境电商高歌猛进!

黑五作为一个类似于中国双11的全球性购物狂欢节&#xff0c;在11月的最后一周拉开帷幕&#xff0c;据有关业内人士称&#xff0c;作为拥有众多全球站的亚马逊电子商务平台在此次黑色星期五的促销狂欢节中表现似乎稍显停滞&#xff0c;其作为电商领域的龙头企业&#xff0c;没能…