设计模式学习笔记 - 开源实战五(下):总结Mybatis中用到的10种设计模式

概述

本章再对 Mybatis 用到的设计模式做一个总结。它用到的设计模式也不少。有些前面章节已经经过了,有些则比较简单。


SqlSessionFactoryBuilder:为什么要用建造者模式来创建 SqlSessionFactory?

在《Mybatis如何权衡易用性、性能和灵活性?》章节,通过一个查询用户的例子,展示了用 Mybatis 进行数据库编程。为方便查看,代码重新摘抄到这里。

public class MybatisDemo {public static void main(String[] args) throws IOException {Reader reader = Resources.getResourceAsReader("mybatis.xml");SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sessionFactory.openSession();UserMapper userMapper = session.getMapper(UserMapper.class);UserDo userDo = userMapper.selectById(8);// ...}
}

针对这段代码,请你思考下下面这个问题。

之前讲到建造者模式时,我们使用 Builder 类来创建对象,一般都是先级联一组 setXXX() 方法来设置属性,然后再调用 builder() 方法创建最终的对象。但是,在上面这段代码中,通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 并不符合这个套路。它既没有 setter 方法,而且 builder() 方法也并非无参,需要传递参数。此外,从上面的代码来看,SqlSessionFactory 对象的创建过程也并不复杂。那直接通过构造函数来创建 SqlSessionFactory 不就行了吗?为什么还要借助建造者模式创建 SqlSessionFactory 呢?

要回答这个问题,先要看下 SqlSessionFactoryBuilder 类的源码。源码如下所示:

public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader) {return build(reader, null, null);}public SqlSessionFactory build(Reader reader, String environment) {return build(reader, environment, null);}public SqlSessionFactory build(Reader reader, Properties properties) {return build(reader, null, properties);}public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
}

SqlSessionFactoryBuilder 类中有大量的 build() 重载函数。为了方便你查看,以及待会儿跟 SqlSessionFactory 类的代码做对比,我们把重载函数抽象出来,贴到这里。

public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader);public SqlSessionFactory build(Reader reader, String environment);public SqlSessionFactory build(Reader reader, Properties properties);public SqlSessionFactory build(Reader reader, String environment, Properties properties);public SqlSessionFactory build(InputStream inputStream);public SqlSessionFactory build(InputStream inputStream, String environment);public SqlSessionFactory build(InputStream inputStream, Properties properties);public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) ;// 上面的所有方法,最终都会调用这个方法public SqlSessionFactory build(Configuration config);
}

我们知道,如果一个类包含很多成员变量,而构建对象并不需要设置所有的成员变量,只需要选择性地设置其中几个就可以了。为了满足这样的构建需求,就要定义多个包含不同参数列表的构造函数。为了避免构造函数过多、参数列表过长,我们一般通过无参构造函数加 setter 方法或者通过建造者模式来解决。

从建造者模式的设计初衷上来看,SqlSessionFactoryBuilder 虽然带有 Builder() 后缀,但不要被它的名字所迷惑,它并不是标准的建造者模式。一方面,原始类 SqlSessionFactory 只需要一个参数,并不复杂。另一方面,Builder 类 SqlSessionFactoryBuilder 仍然定义了 n 多个包含不同参数列表的构造函数。

实际上,SqlSessionFactoryBuilder 设计的初衷只不过是为了简化开发。因为构建 SqlSessionFactory 需要先构建 Configuration,而构建 Configuration 是非常复杂的,需要做很多工作,比如配置的读取、解析、创建 n 多对象等。为了将构建 SqlSessionFactory 的过程隐藏起来,对程序员透明,Mybatis 就设计了 SqlSessionFactoryBuilder 类封装这些构建细节。

SqlSessionFactory:到底属于工厂模式还是建造者模式?

在上面那段 Mybatis 示例代码中,我们通过 SqlSessionFactoryBuilder 创建了 SqlSessionFactory,然后再通过 SqlSessionFactory 创建了 SqlSession。刚刚讲了 SqlSessionFactoryBuilder,现在再来看下 SqlSessionFactory

从名字上,你可能已经猜到,SqlSessionFactory 是一个工厂类,用到的设计模式是工厂模式。不过,它跟 SqlSessionFactoryBuilder 类似,名字有很大的迷惑性。实际上,它并不是标准的工厂模式。为什么这么说呢?我们先来看下 SqlSessionFactory 类的源码。

public interface SqlSessionFactory {SqlSession openSession();SqlSession openSession(boolean autoCommit);SqlSession openSession(Connection connection);SqlSession openSession(TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType);SqlSession openSession(ExecutorType execType, boolean autoCommit);SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType, Connection connection);Configuration getConfiguration();
}

SqlSessionFactory 是一个接口,DefaultSqlSessionFactory 是它的唯一实现类。DefaultSqlSessionFactory 源码如下所示:

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}@Overridepublic SqlSession openSession(boolean autoCommit) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);}@Overridepublic SqlSession openSession(ExecutorType execType) {return openSessionFromDataSource(execType, null, false);}@Overridepublic SqlSession openSession(TransactionIsolationLevel level) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);}@Overridepublic SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {return openSessionFromDataSource(execType, level, false);}@Overridepublic SqlSession openSession(ExecutorType execType, boolean autoCommit) {return openSessionFromDataSource(execType, null, autoCommit);}@Overridepublic SqlSession openSession(Connection connection) {return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);}@Overridepublic SqlSession openSession(ExecutorType execType, Connection connection) {return openSessionFromConnection(execType, connection);}@Overridepublic Configuration getConfiguration() {return configuration;}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);final Executor executor = configuration.newExecutor(tx, execType);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();}}private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;}final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}// ...
}

SqlSessionFactoryDefaultSqlSessionFactory 的源码来看,它的设计非常类似刚刚的 SqlSessionFactoryBuilder,通过重载多个 openSession() 函数,支持通过组合 autoCommitExecutorTransaction 等不同的参数,创建 SqlSession 对象。标准的工厂模式通过 type 来创建继承同一个父类的不同子类对象,而这里只不过是通过传递进来的不同参数,来创建同一个类的对象。所以,它更像建造者模式。

虽然设计思路基本一致,但一个叫 xxxBuilderSqlSessionFactoryBuilder),一个叫 xxxFactorySqlSessionFactory)。而且,叫 xxxBuilder 的也并非标准的建造者模式,叫 xxxFactory 的也并非标准的工厂模式。所以,我个人觉得,Mybatis 对这部分代码的设计还是值得优化的。

实际上,这两个类的作用只不过是为了创建 SqlSession 对象,没有其他作用。所以,我更建议参照 Spring 的设计思路,把 SqlSessionFactoryBuilderSqlSessionFactory 的逻辑,放到一个叫 “ApplicationContext” 的类中。让这个类来全权负责读入配置文件,创建 Configuration,生成 SqlSession

BaseExecutor:模板模式跟普通的继承有什么区别?

如果查阅 SqlSessionDefaultSqlSession 的源码,你会发现,SqlSession 执行 SQL 的业务逻辑,都是委托给了 Executor 来实现。Executor 相关的类主要是用来执行 SQL。其中,Executor 本身是一个接口;BaseExecutor 是一个抽象类,实现了 Executor 接口;而 BatchExecutorSimpleExecutorReuseExecutor 三个类继承 BaseExecutor 抽象类。

BatchExecutorSimpleExecutorReuseExecutor 三个类跟 BaseExecutor 是简单的继承关系,还是模板模式关系呢?我们看一下 BaseExecutor 的源码就清楚了。

public abstract class BaseExecutor implements Executor {// ...@Overridepublic 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);}@Overridepublic List<BatchResult> flushStatements() throws SQLException {return flushStatements(false);}public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {if (closed) {throw new ExecutorException("Executor was closed.");}return doFlushStatements(isRollBack);}// ...@Overridepublic <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);return doQueryCursor(ms, parameter, rowBounds, boundSql);}// ...protected abstract int doUpdate(MappedStatement ms, Object parameter)throws SQLException;protected abstract List<BatchResult> doFlushStatements(boolean isRollback)throws SQLException;protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;// ...private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}// ...}

模板模式基于继承来实现代码复用。如果抽象类中包含模板方法,模板方法调用待子类实现的抽象方法,那这一般就是模板模式的代码实现。而且,在命名上,模板方法与抽象方法一般是一一对应地,抽象方法在模板方法前面多一个 “do”,比如,在 BaseExecutor 类中,其中一个模板方法叫做 update(),那对应地抽象方法叫做 doUpdate()

SqlNode:如何利用解释器模式来解析动态 SQL?

支持配置文件中编写动态 SQL,是 Mybatis 一个非常强大的功能。所谓动态 SQL,就是在 SQL 中可以包含在 trim、if、#{} 等语法标签,在运行时根虎条件来生成不同的 SQL。这么说比较抽象,我举个例子解释下。

<update id="update" parameterType="com.example.User">UPDATE user <trim prefix="SET" prefixOverrides=","><if test="name != null and name != ''">name = #{name}</if><if test="age != null and age != ''">, age = #{age}</if><if test="birthday != null and birthday != ''">, birthday = #{birthday}</if></trim>where id = #{id}
</update>

显然,动态 SQL 的语法规则是 Mybtis 自定义的。如果想要根据语法规则,替换掉动态 SQL 中的动态元素,生成真正可以执行的 SQL 语句,Mybatis 还需要实现对应的解释器。这一部分功能就可以看作是解释器模式的应用。实际上,如果你去查看它的代码实现,你会发现,它跟我们在前面讲解解释器模式时举的例子的代码结构非常相似。

前面提到,解释器模式在解释语法规则时,一般会把语法规则分割成小的单元,特别是可以嵌套的小单元,针对每个小单元来解析,最终再把解析结果合并在一起。这里也不例外。Mybatis 把每个语法小单元叫 SqlNodeSqlNode 的定义如下所示:

public interface SqlNode {boolean apply(DynamicContext context);
}

对应不同的语法小单元,Mybatis 定义的不同 SqlNode 实现类。

在这里插入图片描述

整个解释器的调用入口在 DynamicSqlSource.getBoundSql() 方法中,它调用了 rootSqlNode.apply(context) 方法。

ErrorContext:如何实现一个线程唯一的单例模式?

在单例模式章节,我们讲到单例模式时进程唯一的。同时,还讲到单例模式的几种变形,比如线程唯一的单例、集群唯一的单例等等。在 Mybatis 中,ErrorContext 这个类就是标准的单例的变形:现成唯一实例。

它的代码实现如下所示。它基于 Java 的 ThreadLocal 类实现。

public class ErrorContext {private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();private ErrorContext stored;private String resource;private String activity;private String object;private String message;private String sql;private Throwable cause;private ErrorContext() {}public static ErrorContext instance() {ErrorContext context = LOCAL.get();if (context == null) {context = new ErrorContext();LOCAL.set(context);}return context;}// ...
}

Cache:为什么要用装饰器模式而不设计成继承子类?

前面提到,Mybatis 是一个 ORM 框架。实际上,它不只是简单地完成了对象和数据库之间的互相转化,还提供了很多其他功能,比如缓存、事务等。接下来,再讲讲它的缓存实现。

在 Mybatis 中,缓存功能由接口 Cache 定义。PrepetualCache 类是最基础的缓存类,是一个大小无限的缓存。此外,Mybatis 还设计了 9 个包裹 PrepetualCache 的类装饰器,用来实现功能增强。它们分别是:FifoCacheLoggingCacheLruCacheScheduleCacheSerializedCacheSoftCacheSynchronizedCacheWeakCacheTransactionCache

public interface Cache {String getId();void putObject(Object key, Object value);Object getObject(Object key);Object removeObject(Object key);void clear();int getSize();ReadWriteLock getReadWriteLock();
}public class PerpetualCache implements Cache {private final String id;private Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic int getSize() {return cache.size();}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic ReadWriteLock getReadWriteLock() {return null;}
}

另外 9 个装饰器的代码结构都类似,我们只浆砌砖的 LruCache 的源码贴到这里。从代码中可以看出,它是标准的装饰器模式的代码实现。

public class LruCache implements Cache {private final Cache delegate;private Map<Object, Object> keyMap;private Object eldestKey;public LruCache(Cache delegate) {this.delegate = delegate;setSize(1024);}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}public void setSize(final int size) {keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {private static final long serialVersionUID = 4267176411845948333L;@Overrideprotected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {boolean tooBig = size() > size;if (tooBig) {eldestKey = eldest.getKey();}return tooBig;}};}@Overridepublic void putObject(Object key, Object value) {delegate.putObject(key, value);cycleKeyList(key);}@Overridepublic Object getObject(Object key) {keyMap.get(key); //touchreturn delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyMap.clear();}@Overridepublic ReadWriteLock getReadWriteLock() {return null;}private void cycleKeyList(Object key) {keyMap.put(key, key);if (eldestKey != null) {delegate.removeObject(eldestKey);eldestKey = null;}}
}

之所以 Mybatis 采用装饰器模式来实现缓存功能,是因为装饰器模式采用了组合,而非继承,更加灵活,能够有效地避免继承关系的组合爆炸。

PropertyTokenizer:如何利用迭代器模式实现一个属性解析器?

前面章节讲过,迭代器模式常用来替代 for 循环遍历集合。Mybatis 的 PropertyTokenizer 类实现了 Java Iterator 接口,是一个迭代器,用来对配置属性进行解析。具体代码如下所示:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {private String name;private final String indexedName;private String index;private final String children;public PropertyTokenizer(String fullname) {int delim = fullname.indexOf('.');if (delim > -1) {name = fullname.substring(0, delim);children = fullname.substring(delim + 1);} else {name = fullname;children = null;}indexedName = name;delim = name.indexOf('[');if (delim > -1) {index = name.substring(delim + 1, name.length() - 1);name = name.substring(0, delim);}}public String getName() {return name;}public String getIndex() {return index;}public String getIndexedName() {return indexedName;}public String getChildren() {return children;}@Overridepublic boolean hasNext() {return children != null;}@Overridepublic PropertyTokenizer next() {return new PropertyTokenizer(children);}@Overridepublic void remove() {throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");}
}

实际上, PropertyTokenizer 也并非标准的迭代器类。它将配置的解析、解析之后的元素、迭代器,这三部分代码都耦合在一个类中,所以看起来稍微有点难懂。不过这样做的好处是能够做到惰性解析。我们不需要事先将整个配置,解析成多个 PropertyTokenizer 对象。只有当我们在调用 next() 函数时,才会解析其中的部分配置。

Log:如何使用适配器模式来适配不同的日志框架

在适配器模式章节我们讲过,Sl4j 为了统一各个不同的日志框架(Log4j、JCL、Logback 等),提供了一套统一的日志接口。不过,Mybatis 并没有直接使用 Sl4j 提供的统一日志规范,而是自己又重复造轮子,定义了一套自己的日志访问接口。

public interface Log {boolean isDebugEnabled();boolean isTraceEnabled();void error(String s, Throwable e);void error(String s);void debug(String s);void trace(String s);void warn(String s);
}

针对 Log 接口,Mybatis 还提供了各种不同的实现类,分别使用不同的日志框架来实现 Log 接口。
在这里插入图片描述

这几个类的代码结构基本一致。我们把其中的 Log4jImpl 的源码贴到下方。在适配器模式中,传递给适配器构造函数的是被适配的类对象,而这里是 clazz (相当于日志名称 name),所以,从代码实现上来讲,它并非标准的适配器模式。但是,从应用场景上看,这里确实又起到了适配的作用,是典型的适配器模式的应用场景。

public class Log4jImpl implements Log {private static final String FQCN = Log4jImpl.class.getName();private final Logger log;public Log4jImpl(String clazz) {log = Logger.getLogger(clazz);}@Overridepublic boolean isDebugEnabled() {return log.isDebugEnabled();}@Overridepublic boolean isTraceEnabled() {return log.isTraceEnabled();}@Overridepublic void error(String s, Throwable e) {log.log(FQCN, Level.ERROR, s, e);}@Overridepublic void error(String s) {log.log(FQCN, Level.ERROR, s, null);}@Overridepublic void debug(String s) {log.log(FQCN, Level.DEBUG, s, null);}@Overridepublic void trace(String s) {log.log(FQCN, Level.TRACE, s, null);}@Overridepublic void warn(String s) {log.log(FQCN, Level.WARN, s, null);}}

总结

本章,讲解了 Mybatis 中用到的 8 种设计模式,它们分别是:建造者模式、工程模式、模板模式、解释权模式、单例模式、装饰器模式、适配器模式。再加上上篇文章的职责链模式和动态代理,总共讲了 10 种设计模式。

从两篇文章的讲解中,不知道你发现没有,Mybatis 对很多设计模式的实现,都并非标准的代码实现,都做了比较多的自我改进。实际上,这就是所谓的灵活应用,只借鉴不照搬,根据具体问题针对性地去解决。

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

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

相关文章

鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班

本篇通过拆解一段很简单的汇编代码来快速认识汇编&#xff0c;为读懂鸿蒙汇编打基础.系列篇后续将逐个剖析鸿蒙的汇编文件. 汇编很简单 第一&#xff1a; 要认定汇编语言一定是简单的&#xff0c;没有高深的东西&#xff0c;无非就是数据的搬来搬去&#xff0c;运行时数据主要…

高等学校数字化校园平台介绍

高等学校信息化建设已经进入到跨业务领域信息共享、建立全校统一集成的信息系统阶段&#xff0c;目标是实现整个学校的系统集成、信息共享与工作协同。因此&#xff0c;如何将众多应用系统中大量的信息进行科学、规范的定义和分类&#xff0c;使信息有序流通、保证信息的一致性…

IP定位技术企业网络安全检测

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;成为企业运营中不可忽视的一环。在众多网络安全技术中&#xff0c;IP定位技术以其独特的优势&#xff0c;为企业网络安全检测提供了强有力的支持。本文将深入探讨IP定位技术在企业网络安全检测中的应用及其…

使用通义千问,识别需求文档中的输入输出信号存储到excel表格,完成对需求的初步分析

操作步骤如下&#xff1a; 第一步&#xff0c;提取需求的输入输出信号为json格式&#xff0c; 提示词如下&#xff1a; 车速自动闭锁 使能条件&#xff08;a&b&c&d&e&f&#xff09; a. 电源状态为 ON&#xff08;PowerMode ON&#xff09; b. 主驾门锁…

idm下载速度慢解决办法 idm批量下载怎么用 idm优化下载速度 Internet Download Manager解决下载速度慢的方法教程

IDM (Internet Download Manager)是一款兼容性大&#xff0c;支持多种语言的下载管理软件&#xff0c;它可以自动检测并下载网页上的内容&#xff0c;这正是这一优点&#xff0c;使得它受到了广大用户的喜爱。但是在下载的过程中&#xff0c;我们会遇到idm下载速度慢怎么回事&a…

ubuntu搭建node私库Verdaccio

ubuntu搭建node私库Verdaccio Verdaccio 是一个轻量级的私有 npm 代理注册服务器&#xff0c;它是开源的&#xff0c;可以帮助你设置和维护企业内部的 npm 包的存储库。使用 Verdaccio 可以让你完全控制包的发布流程、依赖关系以及访问策略。这篇文章将指导你如何在 Ubuntu 系…

【Elasticsearch】安装配置与使用

1 前期准备 1.1 环境准备 麒麟ARM 64位操作系统 1.2 安装包准备 Elasticsearch下载地址: https://www.elastic.co/cn/downloads/elasticsearch 2 部署elasticsearch 2.1 创建es专用用户 注意&#xff1a;ES不能使用root用户来启动&#xff0c;必须使用普通用户来安装启…

【百度Apollo】探索自动驾驶:小白教学如何使用 Dreamview 播放数据包

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引入一、Dreamview 简介二、使用 Dreamview 具体步骤步骤一&#xff1a;进入 Apollo Docker 环境步骤二&#xff…

.net报错异常及常用功能处理总结(持续更新)

.net报错异常及常用功能处理总结---持续更新 1. WebApi dynamic传参解析结果中ValueKind Object处理方法问题描述方案1&#xff1a;(推荐&#xff0c;改动很小)方案2&#xff1a; 2.C# .net多层循环嵌套结构数据对象如何写对象动态属性赋值问题描述JavaScript动态属性赋值.net…

初步认识Vscode

4.26初步认识Vscode &#xff08;一&#xff09;快捷键的使用 1. 打开控制端 ctrl ~2. 结束终端 ctrl c3. 多行同时对齐输出 按住shift alt 光标多选4. 多行同时任意位置输出 按住alt 光标单点你想要输入的位置5. 代码太长了&#xff0c;想混行编辑 alt z6. 打开设置控制…

记录一次大数据量接口优化过程

问题描述 记录一次大数据量接口优化过程。最近在优化一个大数据量的接口&#xff0c;是提供给安卓端APP调用的&#xff0c;因为安卓端没做分批次获取&#xff0c;接口的数据量也比较大&#xff0c;因为加载速度超过一两分钟&#xff0c;所以导致接口超时的异常&#xff0c;要让…

【网络原理】TCP协议的相关机制(确认应答、超时重传)

系列文章目录 【网络通信基础】网络中的常见基本概念 【网络编程】Java网络编程中的基本概念及实现UDP、TCP客户端服务器程序&#xff08;万字博文&#xff09; 【网络原理】UDP协议的报文结构 及 校验和字段的错误检测机制&#xff08;CRC算法、MD5算法&#xff09; 文章目…

# 谷歌 Chrome 浏览器无法安装插件的解决方法

谷歌 Chrome 浏览器无法安装插件的解决方法 运用开发模式安装 安装步骤&#xff1a; 1、 将 XX.crx 插件的扩展名改成 .zip 或者 .rar 并解压到文件夹 XX 目录。 1&#xff09;如&#xff1a;下载的 前端框架 vue.js 插件 nhdogjmejiglipccpnnnanhbledajbpd-6.6.1-Crx4Chro…

面试重点2:网页访问不了,从服务器层面如何排查

从服务器层面排查网页访问问题可以按照以下步骤进行&#xff1a; 1. 检查网络连接 确保服务器的网络连接正常&#xff0c;可以通过 ping 命令测试网络是否通畅&#xff0c;例如 ping www.example.com。 2. 排查 DNS 问题 如果访问域名无法解析&#xff0c;可能是 DNS 配置问…

邦注科技即热式节能模温机 模温机的工作原理

模温机是一种用于控制模具温度的设备&#xff0c;主要用于塑料注塑、压铸、橡胶成型等工艺中。 其工作原理主要包括以下几个步骤&#xff1a; 加热阶段&#xff1a; 当模具需要加热时&#xff0c;双温模温机会启动加热系统&#xff0c;将热传导油或热传导水加热至设定温度。加…

Spring Cloud学习笔记(Hystrix):execute,queue,observe,toObservable样例和特性

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、Overview2、execute()2.1、Overview2.2、示例 3、queue()3.1、Overview3.2、示例 4、observe()4.1、Overview4.2、示例 5、toObservable()5.1、observe()和toObservable()的区别 1、Overview 我们知道Hystrix…

HDFS架构

HDFS 是一个主从 Master/Slave 架构一个 HDFS 集群包含一个 NameNode,这是一个 Master Server,用来管理文件系统的命名空间,以及协调客户端对文件的访问一个 HDFS 集群包含多个 DataNode,用来存储数据HDFS 会对外暴露一个文件系统命名空间,并允许用户数据以文件的形式进行存储在…

iOS实现一个高性能的跑马灯

效果图 该跑马灯完全通过CATextLayer 实现&#xff0c;轻量级&#xff0c;并且通过 系统的位移动画实现滚动效果&#xff0c;避免了使用displaylink造成的性能瓶颈&#xff0c;使用系统动画&#xff0c;系统自动做了很多性能优化&#xff0c;实现更好的性能&#xff0c;并使用…

java设计模式 -- 工厂模式

1、基本概念 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一&#xff0c;这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 工厂模式提供了一种创建对象的方式&#xff0c;而无需指定要创建的具体类。 工厂…

python与上位机开发day04

模块和包、异常、PyQt5 一、模块和包 1.1 模块 Python中模块就是一个.py文件&#xff0c;模块中可以定义函数&#xff0c;变量&#xff0c;类。模块可以被其他模块引用 1.1.1 导入模块 """ 导入格式1&#xff1a; import 模块名 使用格式&#xff1a; …