Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
一: 前言
我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。
1:背景
我们基于Mybatis作为后台Orm框架进行编码的时候,有两种方式。
//编码方式1
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.queryAllUser(Map map);
//有同学质疑为毛我从来没有sqlSesseion.getMapper(..)是因为我们使用Spring或者
//boot的时候右边给你封装了,导致你直接在Service里边把UserDao进行了注入,直接
//=右边的,你可能压根就没有机会写。//编码方式2
sqlSession.select("com.shit.user.UserDao.queryAllUser",Map map);
我们提到了,最为底层的是下边这种方式,第一种方式是基于动态代理的技术实现的,底层也是第二种方式,基于此我们开始探索了动态代理的这种方式,整套流程下来是怎么玩的。
UserDao userDao = SqlSession.getMapper(User.class)
userDao.queryuserById();
userDao.queryUsers();
2:代理设计模式(动态代理)的一些特点(简述)
这里的userDao是DAO接口的实现类的对象,那么我们就会有一个疑问,UserDao这个接口实现类在哪里呢?
这个Dao接口的实现类我们是见不到的,因为这里是一个典型的动态字节码技术,这个类是在虚拟机当中运行时创建的。虚拟机运行结束的时候,这个类就消失了
这和我们之前的这些类不一样,我们之前写的这些类就是实实在在的文件,在我们的硬盘当中,通过JVM的类加载的方式读到虚拟机的内存当中。这是我们传统意义上的一个类的创建的方式,通过复杂的类加载机制将这个类加载到JVM虚拟机当中之后,我们在这个类的Class对象创建出来。
动态字节码技术的特点是类的文件自始至终就没有,直接就在虚拟机当中的内存当中去创建,进而创建这个类的Class对象。是JVM在运行时的时候基于动态字节码技术将这个类创建出来的操作。
这就是为什么我们知道有这么一个类的存在,有这么一个类的对象的存在,但是我们就看不到的原因。
创建完成之后,就按照多态的特点,代理对象地址保存在这个接口的引用类型下边。按照多态的原则,所以,我们才可以有这个效果。
3:使用代理设计模式场景
1):为原始对象(目标)增加额外功能
ps:如果是和原始功能没有半毛钱关系的内容才叫做额外功能,与原始功能有关系的功能就不太算是额外功能了,这种情况下推进使用装饰器模式。
这个场景典型的Spring的AOP的功能。
2):远程代理(远程代理)
ps:远程代理:这时候额外功能就变成1:网络连接:2:数据传输嘛。这个最典型的就是Dubbo,远程代理代理的是远程服务。
3):无中生有(只有接口,除了接口毛都没有)
ps:这个最符合的就是Mybatis的Dao层的实现类的处理了。接口实现类这个东西,我们真的看不见,但是运行时确实可以体现出来。
在这三个场景下,我们需要考虑用到代理设计模式,第三种是典型的动态代理。而动态代理的典型的编码是:
Proxy.newProxyInstance(AssistantToHistoryPriceRule.class.getClassLoader(),new Class[]{BillSystemConfigGateWay.class}, new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("额外功能输出......");return null;}});
二:实现类该如何去实现预定的功能呢?
1:猜想Mybatis的代理流程
我们现在有一个Ineterface接口中有这样的一个方法,实现类实现之后应该大致张这个样子:
以下这个是我们的大致流程猜想,当然最终结果肯定是与Mybatis的具体实现大差不差的。
interface UserDao{List<User> queryAllUsers();save(User user)
}UserDaoImpl implements UserDAO {queryAllUsers(){sqlSession.select(....);}save(){sqlSession.insert("namespace.id",参数);}
}

类加载器,接口,额外功能处理Handler三个条件满足之后,运行时会把这个代理对象给创建出来。
2:模拟Mybatis代理对象创建
@Testpublic void testProxy() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();Class[] interfaces = new Class[]{UserDAO.class};UserDAO userDAO = (UserDAO) Proxy.newProxyInstance(TestMybatis.class.getClassLoader(),interfaces,new MyMapperProxy(sqlSession,UserDAO.class));List<User> users = userDAO.queryAllUsersByPage();for (User user : users) {System.out.println("user = " + user);}}
public class MyMapperProxy implements InvocationHandler {private SqlSession sqlSession;private Class daoClass;public MyMapperProxy(SqlSession sqlSession, Class daoClass) {this.sqlSession = sqlSession;this.daoClass = daoClass;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(daoClass.getName() + "." + method.getName());return sqlSession.selectList(daoClass.getName() + "." + method.getName());}
}
这一步的创建代理的操作就等同于我们之前当中的SqlSession.getMapper(UserDao.class)的操作。

三:Mybatis代理对象创建的源码如下
Mybatis当中完成代理创建的核心类型是:MapperProxy和MapperProcyFactory
MapperProcyFactory:是个工厂,他的作用就是创建代理,工厂底层设计的过程中完成的就是Proxy.newProxyInstance(),为我们创建代理对象
MapperProxy:实现了InvocationHandler接口,他就是用调用具体method的。
/*** @author Lasse Voss*/
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);}}
/*** @author Clinton Begin* @author Eduardo Macarron*/
public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class<T> mapperInterface;private final Map<Method, MapperMethod> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic 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);}private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod;}@UsesJava7private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class<?> declaringClass = method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);}/*** Backport of java.lang.reflect.Method#isDefault()*/private boolean isDefaultMethod(Method method) {return (method.getModifiers()& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC&& method.getDeclaringClass().isInterface();}
}
public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {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);result = sqlSession.selectOne(command.getName(), param);}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;}private Object rowCountResult(int rowCount) {final Object result;if (method.returnsVoid()) {result = null;} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {result = rowCount;} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {result = (long)rowCount;} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {result = rowCount > 0;} else {throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());}return result;}private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());if (!StatementType.CALLABLE.equals(ms.getStatementType())&& void.class.equals(ms.getResultMaps().get(0).getType())) {throw new BindingException("method " + command.getName()+ " needs either a @ResultMap annotation, a @ResultType annotation,"+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");}Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));} else {sqlSession.select(command.getName(), param, method.extractResultHandler(args));}}private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {List<E> result;Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);result = sqlSession.<E>selectList(command.getName(), param, rowBounds);} else {result = sqlSession.<E>selectList(command.getName(), param);}// issue #510 Collections & arrays supportif (!method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {return convertToArray(result);} else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result);}}return result;}private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {Cursor<T> result;Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);} else {result = sqlSession.<T>selectCursor(command.getName(), param);}return result;}private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {Object collection = config.getObjectFactory().create(method.getReturnType());MetaObject metaObject = config.newMetaObject(collection);metaObject.addAll(list);return collection;}@SuppressWarnings("unchecked")private <E> Object convertToArray(List<E> list) {Class<?> arrayComponentType = method.getReturnType().getComponentType();Object array = Array.newInstance(arrayComponentType, list.size());if (arrayComponentType.isPrimitive()) {for (int i = 0; i < list.size(); i++) {Array.set(array, i, list.get(i));}return array;} else {return list.toArray((E[])array);}}private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {Map<K, V> result;Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);} else {result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());}return result;}public static class ParamMap<V> extends HashMap<String, V> {private static final long serialVersionUID = -2212268410512043556L;@Overridepublic V get(Object key) {if (!super.containsKey(key)) {throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());}return super.get(key);}}public static class SqlCommand {private final String name;private final SqlCommandType type;public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {final String methodName = method.getName();final Class<?> declaringClass = method.getDeclaringClass();MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);if (ms == null) {if (method.getAnnotation(Flush.class) != null) {name = null;type = SqlCommandType.FLUSH;} else {throw new BindingException("Invalid bound statement (not found): "+ mapperInterface.getName() + "." + methodName);}} else {name = ms.getId();type = ms.getSqlCommandType();if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}}public String getName() {return name;}public SqlCommandType getType() {return type;}private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,Class<?> declaringClass, Configuration configuration) {String statementId = mapperInterface.getName() + "." + methodName;if (configuration.hasStatement(statementId)) {return configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;}for (Class<?> superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = resolveMappedStatement(superInterface, methodName,declaringClass, configuration);if (ms != null) {return ms;}}}return null;}}public static class MethodSignature {private final boolean returnsMany;private final boolean returnsMap;private final boolean returnsVoid;private final boolean returnsCursor;private final Class<?> returnType;private final String mapKey;private final Integer resultHandlerIndex;private final Integer rowBoundsIndex;private final ParamNameResolver paramNameResolver;public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);if (resolvedReturnType instanceof Class<?>) {this.returnType = (Class<?>) resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}this.returnsVoid = void.class.equals(this.returnType);this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);this.mapKey = getMapKey(method);this.returnsMap = this.mapKey != null;this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);this.paramNameResolver = new ParamNameResolver(configuration, method);}public Object convertArgsToSqlCommandParam(Object[] args) {return paramNameResolver.getNamedParams(args);}public boolean hasRowBounds() {return rowBoundsIndex != null;}public RowBounds extractRowBounds(Object[] args) {return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;}public boolean hasResultHandler() {return resultHandlerIndex != null;}public ResultHandler extractResultHandler(Object[] args) {return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;}public String getMapKey() {return mapKey;}public Class<?> getReturnType() {return returnType;}public boolean returnsMany() {return returnsMany;}public boolean returnsMap() {return returnsMap;}public boolean returnsVoid() {return returnsVoid;}public boolean returnsCursor() {return returnsCursor;}private Integer getUniqueParamIndex(Method method, Class<?> paramType) {Integer index = null;final Class<?>[] argTypes = method.getParameterTypes();for (int i = 0; i < argTypes.length; i++) {if (paramType.isAssignableFrom(argTypes[i])) {if (index == null) {index = i;} else {throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");}}}return index;}private String getMapKey(Method method) {String mapKey = null;if (Map.class.isAssignableFrom(method.getReturnType())) {final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);if (mapKeyAnnotation != null) {mapKey = mapKeyAnnotation.value();}}return mapKey;}}}

2:核心对象的拆解
1:MapMethod对象
private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}
}
MapMethod当中需要两个成员变量:SqlCommand+MethodSignature
把Method(Dao接口中的某一个方法的对象,再次包装了一层,包装成了MapMethod对象),增添了SqlCommand+MethodSignature两个成员。
2:SqlCommand对象
包含两个关键成员:
private final String name;
private final SqlCommandType type;private final String name;private final SqlCommandType type;//构造方法public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {final String methodName = method.getName();final Class<?> declaringClass = method.getDeclaringClass();MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);if (ms == null) {if (method.getAnnotation(Flush.class) != null) {name = null;type = SqlCommandType.FLUSH;} else {throw new BindingException("Invalid bound statement (not found): "+ mapperInterface.getName() + "." + methodName);}} else {name = ms.getId();type = ms.getSqlCommandType();if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}}
name是nameSpace+id,type是insert,delete,update,select
3:MethodSignature
方法的入参类型和返回值类型。对SQL入参和SQL执行后的返回值的处理提供类型机制。
到这里我们是不是就有点清晰了,Mybatis中对代理设计模式的应用是这个样子的:
1:这个流程中几个核心的成员
MappedProxyFactory:执行Proxy.newProxyInstance(ClassLoader,UserDao.class,InvocationHandler);
来创建代理对象
MappedProxy:InvocationHandler的实习类,原始功能都没有的这种代理设计模式中的功能模块,
具体进行sqlSession.select,insert,update,delete的模块
MapMethod 是对方法对象的二次封装,我们知道Mybatis中UserDao中的method,可以提供nameSpace.id嘛,
这就是他的作用。
SqlCommand:对象nameSpace.id基于构造方法,放到了name属性里边,type是method增删改查的类型描述
MethodSignature:对方法入参和方法返参的描述

相关文章:
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
一: 前言 我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。 1:背景 我们基于Mybatis作为后台Orm框架进行编码的时候,有两种方式。 //编码方式1 UserDao userDao sqlSession.getMapper(UserDao.class); userDao.quer…...
JDBC的API详解
🍎道阻且长,行则将至。🍓 目录 一、DriverManager 驱动管理类 1.注册驱动 2.获取数据库连接 二、Connection 数据库连接对象 1.获取执行对象 2.事务管理 三、Statement 1.执行DDL、DML语句 2.执行DQL语句 四、ResultSet 以JDBC快速…...
【深度强化学习】(4) Actor-Critic 模型解析,附Pytorch完整代码
大家好,今天和各位分享一下深度强化学习中的 Actor-Critic 演员评论家算法,Actor-Critic 算法是一种综合了策略迭代和价值迭代的集成算法。我将使用该模型结合 OpenAI 中的 Gym 环境完成一个小游戏,完整代码可以从我的 GitHub 中获得…...
SQL注入——文件上传
目录 一,mysql文件上传要点 二,文件上传指令 一句话木马 三,实例 1,判断注入方式 2,测试目标网站的闭合方式: 3,写入一句话木马 4,拿到控制权 一,mysql文件上传…...
【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(新的开篇)”】
目录【ESP32freeRTOS学习笔记】系列新的开篇ESP-IDF对FreeRTOS的适配ESP-IDF环境中使用FreeRTOS的差异性简介关于FreeRTOS的配置关于ESP-IDF FreeRTOS Applications结语【ESP32freeRTOS学习笔记】系列新的开篇 ESP-IDF对FreeRTOS的适配 FreeRTOS是一个可以适用于多个不同MCU开…...
Uipath Excel 自动化系列18-RefreshPivotTable(刷新透视表)
活动描述 RefreshPivotTable(刷新透视表):如果透视表的数据源发生变化,需使用刷新透视表活动,该活动需与Use Excel File 活动选择的 Excel 文件一起使用。 使用如下图: RefreshPivotTable(刷新透视表)属性 属性 作用 Display…...
设计模式之不变模式
在并行软件开发过程中,同步操作是必不可少的。当多线程对同一个对象进行读写操作时,为了保证对象数据的一致性和正确性,有必要对对象进行同步操作,但同步操作对系统性能有损耗。不变模式可以去除这些同步操作,提高并行…...
C++11 map
C11中Map的使用Map是c的一个标准容器,她提供了很好一对一的关系,在一些程序中建立一个map可以起到事半功倍的效果,总结了一些map基本简单实用的操作!1. map最基本的构造函数;map<string , int >mapstring; map&l…...
docker基本命令 - 数据卷
作用 ● 做数据持久化。防止容器一旦停止运行,该容器中运行产生的数据就没了 ● 不同容器之间的数据共享(大鲸鱼背上各个小集装箱之间可以共享数据) 交互式命令使用 docker run -it -v / 宿主机的绝对路径目录:/容器内绝对路径目录 镜像名 docker run -it -v / 宿…...
SQL查漏补缺
有这么一道题,先看题目,表的内容如下 显示GDP比非洲任何国家都要高的国家名称(一些国家的GDP值可能为NULL)。 错误的查询: SELECT name FROM bbcWHERE gdp > ALL (SELECT gdp FROM bbc WHERE region Africa)正确的查询: SE…...
偏向锁撤销
偏向状态 一个对象创建时: 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0。偏向锁是默认是延迟的,不会在程序启动时立…...
Qt版海康MV多相机的采集显示程序
创建对话框工程MultiCamera工程文件MultiCamera.pro#------------------------------------------------- # # Project created by QtCreator 2023-03-11T16:52:53 # #-------------------------------------------------QT core guigreaterThan(QT_MAJOR_VERSION, 4): …...
2023年江苏省职业院校技能大赛中职网络安全赛项试卷-教师组任务书
2023年江苏省职业院校技能大赛中职网络安全赛项试卷-教师组任务书 一、竞赛时间 9:00-12:00,12:00-15:00,15:00-17:00共计8小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 基础设施设置与安全加固、网络安全事件响应、数…...
零基础小白如何自学网络安全成为顶尖黑客?
在成为黑客之前,你需要做两点准备: 1、学一门编程语言。学哪一门不重要,但你要参考一下下面的条例: C语言是Unix系统的基础。它(连同汇编语言)能让你学习对黑客非常重要的知识:内存的工作原理…...
外贸建站如何提高搜索引擎排名,吸引更多潜在客户?
在如今全球贸易日益繁荣的背景下,越来越多的企业开始重视外贸建站,并寻求提高搜索引擎排名以吸引更多潜在客户。 那么,如何才能有效地提高外贸网站的搜索引擎排名呢?本文将为您详细介绍几个有效的方法。 一、关键词优化 关键词…...
计算机网络考研-第一章学
计算机网学习总结第一章计算机系统概述1.1.1 导学1.1.2 操作系统的特征1.2 操作系统的发展与分类1.3 操作系统的运行环境1.3.1 操作系统的运行机制1.3.2 中断和异常1.3.3系统调用:1.3.4 操作系统的体系结构第一章总结第一章计算机系统概述 1.1.1 导学 1.1.2 操作系…...
【分布式版本控制系统Git】| Git概述、Git安装、Git常用命令
目录 一:概述 1.1. 何为版本控制 1.2. 为什么需要版本控制 1.3. 版本控制工具 1.4. Git 简史 1.5. Git 工作机制 1.6. Git和代码托管中心 二:安装 2.1. Git安装 三:常用命令 3.1 设置用户签名 3.2 初始化本地库 3.3 查看本地库…...
【人脸识别】ssd + opencv Eigenfaces 和 LBPH算法进行人脸监测和识别
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言ssd opencv Eigenfaces 和 LBPH算法进行人脸监测和识别1. ssd 目标监测2.opencv的三种人脸识别方法2.1 Eigenfaces2.2 LBPH前言 ssd opencv Eigenfaces 和 LB…...
PMP项目管理项目成本管理
目录1 项目成本管理概述2 规划成本管理3 估算成本4 制定预算5 控制成本1 项目成本管理概述 项目成本管理包括为使项目在批准的预算内完成而对成本进行规划、估算、预测、融资、筹资、管理和控制的各个过程,从而确保项目在批准的预算内完工。核心概念 项目成本管理旨…...
Vue3视频播放器组件Vue3-video-play入门教程
Vue3-video-play适用于 Vue3 的 hls.js 播放器组件 | 并且支持MP4/WebM/Ogg格式。 1、支持快捷键操作 2、支持倍速播放设置 3、支持镜像画面设置 4、支持关灯模式设置 5、支持画中画模式播放 6、支持全屏/网页全屏播放 7、支持从固定时间开始播放 8、支持移动端,移动…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
