MyBatis类型处理器注册器TypeHandlerReister
一、引言
我们知道 mysql的类型和java类型有对应关系,参考此文:https://www.cnblogs.com/jerrylz/p/5814460.html。
那么mybatis是怎么找到这种对应关系帮我们转换的呢?
------------------下面一篇文章讲得很不错,直接摘录在这里-----------------
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6709157.html
1、回顾
上一篇研究的是类型别名注册器TypeAliasRegister,它主要用于将基本类型和用户自定义的类型进行别名注册,将别名及其对应类类型保存在一个HashMap中,方便存取,是映射器映射功能实现的基础,本篇所研究的类型处理器注册器TypeHandlerReister是用来统筹管理类型处理器的,类型处理器是真正用于进行java类型与数据库类型映射的工具。
这一篇我们还是重点研究类型处理器的注册器,有关具体类型处理器的研究放到之后进行。
2、类型处理器
为了研究类型处理器注册器,我们需要对类型处理器有一定的基础和认识,这里简单介绍一下,具体内容可等下一篇。
类型处理器简单点说就是用于处理javaType与jdbcType之间类型转换用的处理器,MyBatis针对诸多Java类型与数据库类型进行了匹配处理。
它主要用于映射器配置文件的工作,在通过类型别名注册器获取类型别名代表的类型之后,就可以使用获取的类型通过类型处理器注册器来得到其对应的JdbcType和对应的类型处理器。
由此可见每个类型处理器都针对两个类型,一个Java类型,一个数据库类型。而类型处理器的作用就是进行二者之间的匹配、对应、转换。
3、类型处理器注册器
类型处理器注册器既能完成类型处理器的注册功能,同时也能对类型处理器进行统筹管理,其内部定义了集合来进行类型处理器的存取,同时定义了存取方法。
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
以上是TypeHandlerRegister中定义的三个Map集合,这三个集合是用来保存类型处理器的注册信息的。
第一种:JDBC_TYPE_HANDLER_MAP,这是一个枚举Map集合,其内部是以JdbcType枚举类中枚举值为键创建的一种集合,这种集合先天存在键(枚举值),它是以数据库类型为键来保存类型处理器,亦即将类型处理器注册到对应的数据库类型上。
第二种:TYPE_HANDLER_MAP,这是一个前套Map集合,内层集合是以数据库类型为键保存处理器,外层集合为以Java类型来保存对应的数据库类型及其处理器,这个集合将三者联系起来,是真正进行三者对应关系匹配的集合。
第三种:ALL_TYPE_HANDLERS_MAP,这个集合中保存着所有的类型处理器,是以类型处理器的类类型为键值保存的,它可以统筹所有的类型处理器(带有统计的效果)。
3.1 基础类型处理器
在创建类型处理器注册器的时候,在其无参构造器中会进行基础类型处理器的注册,这些注册包括三种方式,一种是将以JavaType为键的保存方式,一种是以JdbcTye为键的保存方式,还有一种就是以JavaType与JdbcType为键的保存方式,这最后一种保存方式是一种嵌套的Map集合,前面的两种只是简单的Map集合。
下面将该无参构造器源码罗列:
public TypeHandlerRegistry() {register(Boolean.class, new BooleanTypeHandler());register(boolean.class, new BooleanTypeHandler());register(JdbcType.BOOLEAN, new BooleanTypeHandler());register(JdbcType.BIT, new BooleanTypeHandler());register(Byte.class, new ByteTypeHandler());register(byte.class, new ByteTypeHandler());register(JdbcType.TINYINT, new ByteTypeHandler());register(Short.class, new ShortTypeHandler());register(short.class, new ShortTypeHandler());register(JdbcType.SMALLINT, new ShortTypeHandler());register(Integer.class, new IntegerTypeHandler());register(int.class, new IntegerTypeHandler());register(JdbcType.INTEGER, new IntegerTypeHandler());register(Long.class, new LongTypeHandler());register(long.class, new LongTypeHandler());register(Float.class, new FloatTypeHandler());register(float.class, new FloatTypeHandler());register(JdbcType.FLOAT, new FloatTypeHandler());register(Double.class, new DoubleTypeHandler());register(double.class, new DoubleTypeHandler());register(JdbcType.DOUBLE, new DoubleTypeHandler());register(Reader.class, new ClobReaderTypeHandler());register(String.class, new StringTypeHandler());register(String.class, JdbcType.CHAR, new StringTypeHandler());register(String.class, JdbcType.CLOB, new ClobTypeHandler());register(String.class, JdbcType.VARCHAR, new StringTypeHandler());register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());register(String.class, JdbcType.NCHAR, new NStringTypeHandler());register(String.class, JdbcType.NCLOB, new NClobTypeHandler());register(JdbcType.CHAR, new StringTypeHandler());register(JdbcType.VARCHAR, new StringTypeHandler());register(JdbcType.CLOB, new ClobTypeHandler());register(JdbcType.LONGVARCHAR, new StringTypeHandler());register(JdbcType.NVARCHAR, new NStringTypeHandler());register(JdbcType.NCHAR, new NStringTypeHandler());register(JdbcType.NCLOB, new NClobTypeHandler());register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());register(JdbcType.ARRAY, new ArrayTypeHandler());register(BigInteger.class, new BigIntegerTypeHandler());register(JdbcType.BIGINT, new LongTypeHandler());register(BigDecimal.class, new BigDecimalTypeHandler());register(JdbcType.REAL, new BigDecimalTypeHandler());register(JdbcType.DECIMAL, new BigDecimalTypeHandler());register(JdbcType.NUMERIC, new BigDecimalTypeHandler());register(InputStream.class, new BlobInputStreamTypeHandler());register(Byte[].class, new ByteObjectArrayTypeHandler());register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());register(byte[].class, new ByteArrayTypeHandler());register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());register(JdbcType.LONGVARBINARY, new BlobTypeHandler());register(JdbcType.BLOB, new BlobTypeHandler());register(Object.class, unknownTypeHandler);register(Object.class, JdbcType.OTHER, unknownTypeHandler);register(JdbcType.OTHER, unknownTypeHandler);register(Date.class, new DateTypeHandler());register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());register(JdbcType.TIMESTAMP, new DateTypeHandler());register(JdbcType.DATE, new DateOnlyTypeHandler());register(JdbcType.TIME, new TimeOnlyTypeHandler());register(java.sql.Date.class, new SqlDateTypeHandler());register(java.sql.Time.class, new SqlTimeTypeHandler());register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());register(Instant.class, InstantTypeHandler.class);register(LocalDateTime.class, LocalDateTimeTypeHandler.class);register(LocalDate.class, LocalDateTypeHandler.class);register(LocalTime.class, LocalTimeTypeHandler.class);register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);register(OffsetTime.class, OffsetTimeTypeHandler.class);register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);register(Month.class, MonthTypeHandler.class);register(Year.class, YearTypeHandler.class);register(YearMonth.class, YearMonthTypeHandler.class);register(JapaneseDate.class, JapaneseDateTypeHandler.class);// issue #273register(Character.class, new CharacterTypeHandler());register(char.class, new CharacterTypeHandler());}
通过源码可以发现:MyBatis内置注册的类型处理器很是全面,几乎囊括了所有常用的类型,所以一般情况下我们直接使用内置的类型处理器进行类型处理即可。
3.1.1 针对JavaType,MyBatis内置注册了一下类型:
序号 | javaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | Boolean.class | null | BooleanTypeHandler | 入口2 | |
2 | boolean.class | null | BooleanTypeHandler | ||
3 | Byte.class | null | ByteTypeHandler | ||
4 | byte.class | null | ByteTypeHandler | ||
5 | Short.class | null | ShortTypeHandler | ||
6 | short.class | null | ShortTypeHandler | ||
7 | Integer.class | null | IntegerTypeHandler | ||
8 | int.class | null | IntegerTypeHandler | ||
9 | Long.class | null | LongTypeHandler | ||
10 | long.class | null | LongTypeHandler | ||
11 | Float.class | null | FloatTypeHandler | ||
12 | float.class | null | FloatTypeHandler | ||
13 | Double.class | null | DoubleTypeHandler | ||
14 | double.class | null | DoubleTypeHandler | ||
15 | String.class | null | StringTypeHandler | ||
16 | BigDecimal.class | null | BigDecimalTypeHandler | ||
17 | BigInteger.class | null | BigIntegerTypeHandler | ||
18 | Byte[].class | null | ByteObjectArrayTypeHandler | ||
19 | byte[].class | null | ByteArrayTypeHandler | ||
20 | Object.class | null | UNKNOWN_TYPE_HANDLER | ||
21 | Date.class | null | DateTypeHandler | ||
22 | java.sql.Date.class | null | SqlDateTypeHandler | ||
23 | java.sql.Time.class | null | SqlTimeTypeHandler | ||
24 | Character.class | null | CharacterTypeHandler | ||
25 | char.class | null | CharacterTypeHandler | ||
26 | java.sql.Timestamp.class | null | SqlTimestampTypeHandler |
3.1.2 针对JdbcType,MyBatis内置注册了一下类型:
序号 | javaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | JdbcType.BOOLEAN | BooleanTypeHandler | 入口1 | ||
2 | JdbcType.BIT | BooleanTypeHandler | |||
3 | JdbcType.TINYINT | ByteTypeHandler | |||
4 | JdbcType.SMALLINT | ShortTypeHandler | |||
5 | JdbcType.INTEGER | IntegerTypeHandler | |||
6 | JdbcType.FLOAT | FloatTypeHandler | |||
7 | JdbcType.DOUBLE | DoubleTypeHandler | |||
8 | JdbcType.CHAR | StringTypeHandler | |||
9 | JdbcType.VARCHAR | StringTypeHandler | |||
10 | JdbcType.CLOB | ClobTypeHandler | |||
11 | JdbcType.LONGVARCHAR | ClobTypeHandler | |||
12 | JdbcType.NVARCHAR | NStringTypeHandler | |||
13 | JdbcType.NCHAR | NStringTypeHandler | |||
14 | JdbcType.NCLOB | NClobTypeHandler | |||
15 | dbcType.ARRAY | ArrayTypeHandler | |||
16 | JdbcType.BIGINT | LongTypeHandler | |||
17 | JdbcType.REAL | BigDecimalTypeHandler | |||
18 | JdbcType.DECIMAL | BigDecimalTypeHandler | |||
19 | JdbcType.NUMERIC | BigDecimalTypeHandler | |||
20 | JdbcType.LONGVARBINARY | BlobTypeHandler | |||
21 | JdbcType.BLOB | BlobTypeHandler | |||
22 | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | |||
23 | JdbcType.TIMESTAMP | DateTypeHandler | |||
24 | JdbcType.DATE | DateOnlyTypeHandler | |||
25 | JdbcType.TIME | TimeOnlyTypeHandler |
3.1.3 针对JdbcType和JavaType,MyBatis内置注册了一下类型:
序号 | JavaType | JdbcType | TypeHandler | 入口 | 说明 |
1 | Date.class | JdbcType.DATE | DateOnlyTypeHandler | 入口3 | |
2 | Date.class | JdbcType.TIME | TimeOnlyTypeHandler | ||
3 | Object.class | JdbcType.OTHER | UNKNOWN_TYPE_HANDLER | ||
4 | byte[].class | JdbcType.BLOB | BlobTypeHandler | ||
5 | byte[].class | JdbcType.LONGVARBINARY | BlobTypeHandler | ||
6 | Byte[].class | JdbcType.BLOB | BlobByteObjectArrayTypeHandler | ||
7 | Byte[].class | JdbcType.LONGVARBINARY | BlobByteObjectArrayTypeHandler | ||
8 | String.class | JdbcType.CHAR | StringTypeHandler | ||
9 | String.class | JdbcType.CLOB | ClobTypeHandler | ||
10 | String.class | JdbcType.VARCHAR | StringTypeHandler | ||
11 | String.class | JdbcType.LONGVARCHAR | ClobTypeHandler | ||
12 | String.class | JdbcType.NVARCHAR | NStringTypeHandler | ||
13 | String.class | JdbcType.NCHAR | NStringTypeHandler | ||
14 | String.class | JdbcType.NCLOB | NClobTypeHandler |
3.2 注册入口方法
通过观察源码我们也可以发现这三种注册方式,我在这里将这三种方式的register方法看做三个入口,分别起名为:入口1、入口2、入口3。
其中:
入口1:对应之前介绍的第一种集合(枚举集合),其入口方法为:
public void register(JdbcType jdbcType, TypeHandler<?> handler) {jdbcTypeHandlerMap.put(jdbcType, handler);}
该入口方法用于将类型处理器注册到对应的数据库类型。
入口2:对应之前介绍的第二种嵌套集合(其中内层集合的键为null),其入口方法为:
// java type + handlerpublic <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {register((Type) javaType, typeHandler);}
该入口方法用于将类型处理器注册到对应的Java类型。
入口3:对应之前介绍的第二种嵌套集合,其入口方法为:
// java type + jdbc type + handlerpublic <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {register((Type) type, jdbcType, handler);}
该入口方法用于将类型处理器与Java类型和数据库类型联系起来。
上述的三个入口方法均是对内而设的入口方法,也就是说是用于注册器内部基础类型处理器(MyBatis内置的类型处理器)注册使用的。而MyBatis还提供了自定义类型处理器的功能,也就是说在该类中还提供了对外的自定义类型处理器注册入口。
这么理解:对内就是该方法被类内部调用进行注册,对外就是该方法被类外部的其他类进行调用而进行注册,这里的其他类其实就是XMLConfigBuilder类,它在构建Configuration对象时就会调用对外的注册方法,来将用户自定义的类型处理器注册到注册器中。
对外入口1:只指定包名的情况下,这种情况一般需要配合注解@MappedTypes使用,使用该注解进行JavaType的设置(即注解的value值)
// scanpublic void register(String packageName) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();for (Class<?> type : handlerSet) {//Ignore inner classes and interfaces (including package-info.java) and abstract classesif (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {register(type);}}}
这个入口是一个扫描器,它会使用ResolverUtil工具类在给定包名下扫描所有类,然后进行循环注册,注册之前会进行排除操作,将内部类、接口、抽象类排除在外。这个扫描器针对的就是我们自定义的类型处理器进行注册,这个入口方法会在构建Configuration配置类时由XMLConfigBuilder进行调用,用于将用户自定义的类型处理器注册到注册器中。
上面的入口是在指定包名的情况下进行包扫描来获取包下所有类来进行类型处理器注册,一般会配合注解一起使用,但是如果不配合注解也能成功。如果配合注解指定JavaType,那么它将与对外入口2的情况一致(指定JavaType与TypeHandler),如果没有配合注解,那么就只有TypeHandler,这时候会调用另外一个注册方法,在这个方法中会再次验证是否存在注解,不存在的话,那么验证获取的类是否是TypeReference接口的实现类,如果是其实现类,说明这个类是一个类型处理器,那么再次调用另外一个注册方法,以该类型处理器的原生类型为参数进行调用,在这个方法中需要查询该类型处理器是否有注解@MappedJdbcTypes来指定JdbcType,如果有则以此JdbcType值为数据库类型,如果没有或者是Null类型,则直接将JdbcType置null再调用核心注册方法,将该类型处理器注册到TYPE_HANDLER_MAP集合中,最后还有将该注册器注册到ALL_TYPE_HANDLERS_MAP中用于统一管理。
对外入口2:指定Java类型与类型处理器的情况
public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));}
对于第二种对外入口其实在第一种的情况中已经有所描述,这个方法会调用另外一个注册方法,来使用@MappedJdbcTypes获取jdbcType类型,其余步骤同上。
对外入口3:指定JavaType、JdbcType、TypeHandler三者的情况
// java type + jdbc type + handler typepublic void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));}
这种情况可以直接调用核心注册进行注册即可。
对外入口4:只指定TypeHandler的情况
// Only handler typepublic void register(Class<?> typeHandlerClass) {boolean mappedTypeFound = false;MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);if (mappedTypes != null) {for (Class<?> javaTypeClass : mappedTypes.value()) {register(javaTypeClass, typeHandlerClass);mappedTypeFound = true;}}if (!mappedTypeFound) {register(getInstance(null, typeHandlerClass));}}
这种情况下,需要先验证是否有@MappedType指定JavaType,再验证是否有@MappedJdbcType指定JdbcType,分各种情况进行考虑,这在第一个入口方法中已经描述。
3.3 核心注册方法
虽然拥有诸多对内对外的注册入口方法,但是几乎都会指向核心注册方法,只有对内入口1不会指向核心注册方法,因为第一种对内入口方法的执行效果是往枚举集合JDBC_TYPE_HANDLER_MAP中注册数据库类型处理器。这与其他的注册情况不同,一般我们的注册是指往TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中注册类型处理器。
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {if (javaType != null) {Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);if (map == null || map == NULL_TYPE_HANDLER_MAP) {map = new HashMap<>();typeHandlerMap.put(javaType, map);}map.put(jdbcType, handler);}allTypeHandlersMap.put(handler.getClass(), handler);}
然我来总述一下注册的过程,对上面的情况作何总结:
注册就是要三者兼备,哪三者:javaType、JdbcType、TypeHandler三者兼备,针对自定义类型处理器而言,我们可以通过继承BaseTypeHandler抽象类或者实现TypeHandler接口的方式来进行类型处理器的自定义实现。但是为了使其能在MyBatis中发挥作用,我们要将其注册到类型处理器注册器中。通过简单的配置即可实现,配置方式有两种:
<typeHandlers><package name="com.xx.xx"/><typeHandler handler="com.xx.xx.XxxTypeHandler" javaType="xxx" jdbcType="JdbcType.xxx" /></typeHandlers>
若是用第二种方式配置即可直接进行注册,但是有时我们会省去javaType设置,而使用@MappedTypes注解来指定多个JavaType,或者省去JdbcType配置,采用@MappedJdbcTypes注解来指定多个jdbcType(毕竟配置文件只能指定一个,当需要设置多个时,就只能采用注解的方式实现),这时就需要查询目标处理器类的注解来获取类型,如果既没有在配置文件中配置,也没有通过注解配置,那么就只能置为null(这种情况毕竟,少见,一般我们要自定义类型处理器,必定是有某种类型处理器处理的不满意,我们肯定会指定对应的Java类型与数据库类型,如果听之任之的话我们又何必多此一举呢?)
极端情况就是采用包名配置或者只指定处理器类型进行注册,这时需要逐步查看类型处理器类的注解配置来获取该处理器处理的Java类型与数据库类型,最后在双方都获取到的情况下,三者齐备,调用核心注册方法,将这个类型处理器注册到TYPE_HANDLER_MAP嵌套集合和ALL_TYPE_HANDLERS_MAP集合中。
如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。
MyBatis类型处理器注册器TypeHandlerReister相关推荐
- Mybatis——类型处理器TypeHandler
在日常开发中使用mybatis时,mybatis的mapper.xml.mapper接口.entity实体一般会由mybatis-generator自动生成,其中实体的每个属性与数据库表的列一一对应, ...
- desc 枚举类型id_想让代码更优雅?Mybatis类型处理器了解一下!
明确需求 在设计之初,sys_role表的enabled字段有2个可选值,其中0 代表禁用,1代表启用,而且实体类中我们使用的是Interger类型: 源码展示 /** * 有效标志 */ priva ...
- MyBatis(九):MyBatis类型处理器(TypeHandler)详解
TypeHandler简介 TypeHandler,顾名思义类型转换器,就是将数据库中的类型与Java中的类型进行相互转换的处理器. MyBatis 在设置预处理语句(PreparedStatemen ...
- MyBatis自定义类型处理器 TypeHandler
在项目开发中经常会遇到一个问题: 当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为jav ...
- MyBatis的类型处理器
类型处理器(类型转换器) 1.MyBatis自带一些常见的类型处理器 int - number String - varchar() 2.自定义MyBatis类型处理器 java - 数据库(jdbc ...
- mybatis-plus/mybatis的组件们——拦截器、字段填充器、类型处理器、表名替换、SqlInjector(联合主键处理)
最近有个练手的小例子,大概就是配置两个数据源,从一个数据源读取数据写到另一个数据源,虽然最后做了出来,但是不支持事务...就当是对mybatis-plus/mybatis组件使用方式的记录吧,本次例子 ...
- MyBatis核心源码剖析(SqlSession XML解析 Mapper executor SQL执行过程 自定义类型处理器 缓存 日志)
MyBatis核心源码剖析 MyBatis核心源码剖析 1 MyBatis源码概述 1.1 为什么要看MyBatis框架的源码 1.2 如何深入学习MyBatis源码 1.3 源码分析的5大原则 2 ...
- 【Mybatis】类型处理器TypeHandler的作用与自定义
一.什么是类型处理器 1.类型处理器(TypeHandler) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合 ...
- MyBatis自定义类型处理器(typeHandler)
MyBatis自定义类型处理器(typeHandler) 我们执行sql语句通过PreparedStatement语句实现,PreparedStatement会设置?值,类型处理器帮PreparedS ...
最新文章
- python求5_python(五)——运算符,小整数对象池
- libevent多线程使用bufferevent的那些事
- dreamweaver 疑问
- PHP fgets按行读取字符串和explode分割字符串为数组
- js调用app方法并传承参
- IDEA2019.3 双击启动无反应解决方法
- 「leetcode」135.分发糖果【贪心算法】详细图解
- linux支持ext2格式吗,linux正统标准文件系统ext2详解
- 生成Apk签名证书keystore,openssl与证书,keystore,jks,pem/pk8
- 初学者都能看懂的蒙特卡洛方法以及python实现
- 姓周取名:周姓漂亮有涵养的女孩名字
- h3c交换机配置nat_史上最详细H3C路由器NAT典型配置案例
- 开发Android应用赚钱
- 有什么适合学生党使用的无线蓝牙耳机?安卓平价蓝牙耳机推荐
- 网站 502 解决方法
- AtCoder题解——AtCoder Grand Contest 048——A - atcoder < S
- linux 编辑文件 cat 跳到指定行,Linux基础命令(二)
- echarts5.0引入地图,背景渐变色,航线图,地图阴影
- 深度学习第一讲之深度学习基础
- 你该怎么选Offer
热门文章
- css布局中margin为0,但依然有间距问题的解决方法
- 终于有人把域名和DNS服务器给写明白了
- Android Vibrator 实现灭屏震动功能
- IT资讯精选(2022-09-08)
- 【UVM基础】`uvm_info (“TRACE“, $sformatf(“%m“), UVM_HIGH);
- java 实现的电话号码查询程序 优化版(课程设计)
- 120幅中国近现代国画大师真迹长沙展出
- 初出張(11.5~11.7)
- 个人备考PMP历程分享心得
- 面试问题——英语1 冒险