如何处理枚举类型(上)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

我们会分为上、下两篇分别介绍DAO及Controller层如何处理枚举。重点不是枚举本身,而是希望帮大家开阔眼界,实际开发手动转换枚举也未尝不可。

不了解枚举的同学请先去阅读小册中与枚举相关的其他章节。另外,本文会用到反射及注解相关知识,不熟悉的同学请戳:

反射

注解

这一篇先介绍DAO中枚举相关的处理。

强调一下,这里我直接使用原生MyBatis,而不是通用Mapper或MyBatis-Plus,意在说明MyBatis本身有注意到枚举转换的问题并预留了接口。

环境准备

SQL(注意,这里rest_day是故意使用varchar的,后面会解释)

CREATE TABLE `t_user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`name` varchar(50) DEFAULT '' COMMENT '姓名',`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',`rest_day` varchar(20) DEFAULT '' COMMENT '休息日',`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

pom.xml

<dependencies><!--SpringBoot Web,下篇会用到--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--MyBatis依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--Lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency>
</dependencies>

application.yml

server:port: 8080spring:datasource:url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=trueusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Drivermybatis:mapper-locations: classpath:mapper/**/*.xmlconfiguration:map-underscore-to-camel-case: onlogging:level:com.example.dao: debug

启动类

/*** @author mx*/
@MapperScan("com.example.dao")
@SpringBootApplication
public class MybatisEnumDemoApplication {public static void main(String[] args) {SpringApplication.run(MybatisEnumDemoApplication.class, args);}}

DO

/*** @author mx* @date 2023-11-25 09:56*/
@Data
public class UserDO {/*** 主键id*/private Long id;/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 休息日,实际数据库字段是tinyint或varchar*/private WeekDayEnum restDay;/*** 创建时间*/private Date createTime;/*** 修改时间*/private Date updateTime;/*** 是否删除*/private Boolean deleted;
}

WeekDayEnum

/*** @author mx*/
@Getter
public enum WeekDayEnum {MONDAY(1,"星期一"),TUESDAY(2,"星期二"),WEDNESDAY(3,"星期三"),THURSDAY(4,"星期四"),FRIDAY(5,"星期五"),SATURDAY(6,"星期六"),SUNDAY(7,"星期日");WeekDayEnum(Integer code, String desc) {this.code = code;this.desc = desc;}private final Integer code;private final String desc;
}

UserMapper.java

/*** @author mx*/
public interface UserMapper {/*** 插入用户** @param userDO*/void insertUser(UserDO userDO);/*** 根据id查询* @param id* @return*/UserDO selectUserById(@Param("id") Long id);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserMapper"><resultMap id="BaseResultMap" type="com.example.entity.UserDO"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id"/><result column="name" jdbcType="VARCHAR" property="name"/><result column="age" jdbcType="TINYINT" property="age"/><result column="rest_day" jdbcType="VARCHAR" property="restDay"/><result column="create_time" jdbcType="TIMESTAMP" property="createTime"/><result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/><result column="deleted" jdbcType="BIT" property="deleted"/></resultMap><insert id="insertUser">INSERT INTO t_user (`name`, age, rest_day)VALUES(#{name}, #{age}, #{restDay})</insert><!-- VALUES(#{name, jdbcType=VARCHAR}, #{age, jdbcType=INTEGER}, #{restDay, jdbcType=VARCHAR})--><select id="selectUserById" resultType="com.example.entity.UserDO">SELECT * FROM t_user WHERE id=#{id}</select>
</mapper>
@SpringBootTest
class MybatisEnumTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testInsert() {UserDO userDO = new UserDO();userDO.setName("MyBatis枚举测试");userDO.setAge(18);userDO.setRestDay(WeekDayEnum.FRIDAY);userMapper.insertUser(userDO);}@Testpublic void testSelect() {UserDO userDO = userMapper.selectUserById(1L);System.out.println(userDO);}}

插入测试:

查询测试:

至此,我们完成了最简单的环境搭建。

但你们应该会发现一个神奇的现象:

  • 存入时:private WeekDayEnum restDay(内存) --> MyBatis --> "FRIDAY"(数据库)
  • 查询时:private WeekDayEnum restDay(内存) <-- MyBatis <-- "FRIDAY"(数据库)

在Java和数据库之间,MyBatis承担了中间人的角色,存入时会自动将枚举对象转为字符串,而取出时又把字符串转为枚举对象。

怎么做到的呢?

枚举的存入

我们发现,MyBatis默认对枚举的处理是将枚举的名称插入数据库,而枚举的名称其实就是Enum.name,定义在父类Enum中:

那么MyBatis是在哪里调用WeekDayEnum的name()方法进行转换的呢?

注意截图中这个类的名字:EnumTypeHandler。

MyBatis提供了两个枚举转换器,EnumTypeHandler是其中之一,另一个是EnumOrdinalTypeHandler:

通过源码很容易看出两者的区别

  • EnumTypeHandler:取枚举的name作为值插入数据库
  • EnumOrdinalTypeHandler:取枚举的ordinal作为值插入数据库

name和ordinal被定义在Enum抽象类中,而所有枚举类实际上都会继承Enum,所以每一个枚举对象都有name和ordinal。

MyBatis默认的枚举转换器是EnumTypeHandler,所以开头的SQL我故意把rest_day设置为varchar类型,刚好接收被EnumTypeHandler转换后的枚举字符串。

如果我们改为tinyint,就会报错:

DROP TABLE IF	EXISTS t_user;
CREATE TABLE `t_user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`name` varchar(50) DEFAULT '' COMMENT '姓名',`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',`rest_day` tinyint(1) DEFAULT 1 COMMENT '休息日',`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

重新执行测试案例:

如果数据库rest_day使用的是tinyint类型,需要将MyBatis的默认枚举转换器切换为EnumOrdinalTypeHandler:

mybatis:mapper-locations: classpath:mapper/**/*.xmlconfiguration:map-underscore-to-camel-case: on# 显式声明Mybatis枚举转换器,默认是EnumTypeHandlerdefault-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler

给EnumOrdinalTypeHandler打上断点,再次测试插入:

注意,数据库存的是ordinal,而不是code,Enum的ordinal从0开始,所以4代表FRIDAY

查询:

为什么会打印"FRIDAY"呢?

此时UserDO中的restDay确实是WeekDayEnum对象,打印一个对象通常会调用它的toString(),而Enum父类重写了toString(),实际返回Enum.name,所以打印一个枚举对象最终输出的是Enum.name。

小结:

  • MyBatis默认存入时会使用EnumTypeHandler枚举类型进行转换,调用Enum.name()存入枚举名称
  • 如果希望存入ordinal,可以切换默认枚举转换器为EnumOrdinalTypeHandler

枚举的取出

接着,我们来观察一下数据库中rest_day字段的"FRIDAY"和4被查出来以后如何变成枚举对象FRIDAY的。

同样的,肯定还是EnumTypeHandler和EnumOrdinalTypeHandler帮我们转换的。由于刚才数据库的rest_day已经被我们改为tinyint,所以我们通过EnumOrdinalTypeHandler观察取出时的操作:

好了好了,我知道了,你别说了。我就想知道enums哪来的?

往上看:

在项目启动时MyBatis会初始化EnumTypeHandler,调用构造器时会对private final E[] enums属性赋值。然而神奇的是,type此时enumConstants为null,但从最终结果来看enumConstants是有值的,所以getEnumConstants()内部必然发生了什么。

跟踪进去会发现:

我们之前在设计山寨枚举及反编译枚举时介绍过values()方法和VALUES数组了:

OK,至此EnumOrdinalTypeHandler介绍完了。我们顺便看看EnumTypeHandler:

哦?底层调用了父类Enum的valueOf()方法,根据枚举名称获取枚举实例:

又会跳到Class类的方法中,而且这个方法上面见过了:

T[] universe就是T[] values,而且准备了一个名为m的Map,把枚举的名称作为key,枚举对象本身作为value,把枚举存了起来(你看,又是实用小算法)。

最终Enum.valueOf()其实就是传入枚举名称,然后从Map中得到对应的枚举实例:

所以数据库的"FRIDAY"会被转为FRIDAY对象。

对MyBatis默认提供的枚举转换器的介绍就到这里了。

但不论EnumTypeHandler还是EnumOrdinalTypeHandler,其实都不好用。实际开发中,我们往往使用的不是ordinal或name,而是自己定义的枚举字段,比如code、desc。

默认的两个枚举转换器,一个针对name,另一个针对ordinal,这两个字段属于抽象父类Enum,会在初始化时赋值,而子类特有的code和desc却没用到。

简单版枚举转换器

核心思想是,照着EnumOrdinalTypeHandler抄,搞一个山寨的,然后让MyBatis用我们的Handler转换。

自定义枚举转换器分3步:

  • 编写枚举转换类,实现MyBatis提供的TypeHandler接口
  • 指定type-handlers-package,告诉MyBatis在哪里可以找到我们自定义的转换器
  • 在转换器上用@MappedTypes({WeekDayEnum.class})指定用来处理哪个枚举

TypeHandler是个接口,啥都没有,白手起家太难了:

MyBatis另外提供了BaseTypeHandler让我们继承,EnumOrdinalTypeHandler也是这么干的:

要想自定义枚举转换器,最快的办法是“抄袭”EnumOrdinalTypeHandler:

再把里面的内容全部拷过来:

为了方便确认最终起作用的是我们自定义的MyEnumTypeHandler,稍作修改:

如果最终插入的值会在原来的基础上加100,就说明走了我们自定义的转换器。

然后加上@MappedTypes({WeekDayEnum.class})注解指定处理WeekDayEnum:

最后告诉MyBatis我们自定义的转换器包路径:

mybatis:mapper-locations: classpath:mapper/**/*.xmlconfiguration:map-underscore-to-camel-case: on# 显式声明Mybatis默认枚举转换器(默认EnumTypeHandler)default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler# 指定自定义的枚举转换器路径type-handlers-package: com.example.handlers

传入的是4,经过我们的Handler后实际插入104,说明自定义转换器成功了!但别高兴得太早,上面仅仅是拷贝EnumOrdinalTypeHandler,最终插入的还是ordinal,并不是自定义的枚举字段code。

什么是JdbcType

在正式改代码之前,我们先来解决一个疑惑:JdbcType是什么?

不论是EnumTypeHandler还是EnumOrdinalTypeHandler,setNonNullParameter()的参数列表都有JdbcType:

而且EnumTypeHandler的setNonNullParameter()内部还对JdbcTye做了判断。

所以,什么是JdbcType呢?

MyBatis在org.apache.ibatis.type包下定义了一个JdbcType枚举,用来定义数据库的字段类型,与JdbcType对应的还有JavaType。

如果你跟着上面的代码做了实验,会发现不论怎么修改UserDO,type始终是null:

JdbcType是在SQL中规定的,而不是UserDO中。

其实,只要大家仔细回想,就会发现以前在写SQL语句时好像会指定JdbcType:

<insert id="insertUser">INSERT INTO t_user (`name`, age, rest_day)VALUES(#{name, jdbcType=VARCHAR}, #{age, jdbcType=INTEGER}, #{restDay, jdbcType=INTEGER})
</insert>

如果不指定,MyBatis会自己判断。

现在我把restDay设为jdbcType=INTEGER,再次启动程序就会发现:

注意,这里的4可不是FRIDAY,而是INTEGER:

意思是把Object类型转为INTEGER插入。特别注意,由于上面ps.setObject()方法中传入的是parameter.name(),也就是Enum.name(),所以实际传入的String类型的"FRIDAY"。

当然,此时会报错(实际参数是String,你偏要转为Integer存入):

Cause: java.sql.SQLException: Cannot convert class java.lang.String to SQL type requested due to java.lang.NumberFormatException - For input string: "FRIDAY"

在追踪源码的过程中,有一点很不解:枚举类型最终被归为DECIMAL_UNSIGNED...

但本文不是研究MyBatis源码的,就此打住。总之,必须在MyEnumTypeHandler中对WeekDayEnum进行转换。

注解+反射实现枚举自动类型转换

由于这是demo,且平常我都不写JdbcType,所以我们不考虑type!=null的情况:

/*** 标记需要转换的枚举字段** @author mx*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValue {
}
@Getter
public enum WeekDayEnum {MONDAY(1,"星期一"),TUESDAY(2,"星期二"),WEDNESDAY(3,"星期三"),THURSDAY(4,"星期四"),FRIDAY(5,"星期五"),SATURDAY(6,"星期六"),SUNDAY(7,"星期日");WeekDayEnum(Integer code, String desc) {this.code = code;this.desc = desc;}// 标记最终把code作为枚举的值插入数据库@EnumValueprivate final Integer code;private final String desc;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {if (jdbcType == null) {// 获取WeekDayEnum的所有字段并循环,找到带有@EnumValue注解的字段Field[] declaredFields = type.getDeclaredFields();for (Field declaredField : declaredFields) {// 是否有@EnumValue注解EnumValue enumValue = declaredField.getAnnotation(EnumValue.class);if (enumValue != null) {Object fieldValue = null;try {// 反射获取标记了@EnumValue注解的字段的valuedeclaredField.setAccessible(true);fieldValue = declaredField.get(parameter);} catch (IllegalAccessException e) {e.printStackTrace();}// 设置值ps.setObject(i, fieldValue);return;}}} else {// 不考虑jdbcType!=null的情况ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);}
}

大家自己测试,一般来说是没问题的:

==>  Preparing: INSERT INTO t_user (`name`, age, rest_day) VALUES(?, ?, ?)

==> Parameters: MyBatis枚举测试(String), 18(Integer), 5(Integer)

<==    Updates: 1

注意,存入的是WeekDayEnum.FRIDAY,而数据库显示5,说明这次不是ordinal,而是code。

当然,你也可以把数据库rest_day字段改回VARCHAR,然后把@EnumValue注解加在private String desc上。

还是不要高兴得太早,我们来测一下查询:

是的,MyEnumTypeHandler在取出时会把5当做ordinal解析,所以最终得到的是SATURDAY。

为什么呢?因为当初照抄EnumOrdinalTypeHandler,我们只改了存入的逻辑。

取出的逻辑就不详细说了,大家复制过去看看即可:

@MappedTypes({WeekDayEnum.class})
public class MyEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {private final Class<E> type;private final E[] enums;public MyEnumTypeHandler(Class<E> type) {if (type == null) {throw new IllegalArgumentException("Type argument cannot be null");}this.type = type;this.enums = type.getEnumConstants();if (this.enums == null) {throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");}}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {if (jdbcType == null) {// 获取WeekDayEnum的所有字段并循环,找到带有@EnumValue注解的字段Field[] declaredFields = type.getDeclaredFields();for (Field declaredField : declaredFields) {// 是否有@EnumValue注解EnumValue enumValue = declaredField.getAnnotation(EnumValue.class);if (enumValue != null) {Object fieldValue = null;try {// 反射获取标记了@EnumValue注解的字段的valuedeclaredField.setAccessible(true);fieldValue = declaredField.get(parameter);} catch (IllegalAccessException e) {e.printStackTrace();}// 设置值ps.setObject(i, fieldValue);return;}}} else {// 不考虑jdbcType!=null的情况ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);}}@Overridepublic E getNullableResult(ResultSet rs, String columnName) throws SQLException {// 定义一个变量,接收从数据库查出的rest_dayObject valueFromDB = null;// 确定当初存入时指定了哪个字段Field enumValueField = null;Field[] declaredFields = type.getDeclaredFields();for (Field field : declaredFields) {// 是否有@EnumValue注解EnumValue enumValue = field.getAnnotation(EnumValue.class);if (enumValue != null) {// 找到带有@EnumValue的字段enumValueField = field;// 数据库返回了ResultSet,也即是查询结果集,我们可以从中获取restDay的值valueFromDB = rs.getObject(columnName, enumValueField.getType());break;}}if (enumValueField == null) {// 如果没有标注@EnumValue,还是按默认的解析返回return getResultByOrdinal(rs, columnName);}// 遍历WeekDayEnum的所有实例,反射获取每个实例中标注了@EnumValue的字段值并比较enumValueField.setAccessible(true);for (E weekday : enums) {Object value = null;try {value = enumValueField.get(weekday);if (valueFromDB.equals(value)) {// 值相等,返回对于的枚举对象return weekday;}} catch (IllegalAccessException e) {e.printStackTrace();}}return null;}private E getResultByOrdinal(ResultSet rs, String columnName) throws SQLException {int ordinal = rs.getInt(columnName);if (ordinal == 0 && rs.wasNull()) {return null;}return toOrdinalEnum(ordinal);}private E toOrdinalEnum(int ordinal) {try {return enums[ordinal];} catch (Exception ex) {throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + type.getSimpleName() + " by ordinal value.", ex);}}@Overridepublic E getNullableResult(ResultSet rs, int columnIndex) {return null;}@Overridepublic E getNullableResult(CallableStatement cs, int columnIndex) {return null;}}

MyBatis-Plus对枚举的处理

有时候就是这么巧,万万没想到MyBatis-Plus的处理方式和我们惊人地相似。

3.1.0开始,如果你无需使用原生枚举,可配置默认枚举来省略扫描通用枚举配置

  • 升级说明:
    3.1.0 以下版本改变了原生默认行为,升级时请将默认枚举设置为EnumOrdinalTypeHandler
  • 影响用户:
    实体中使用原生枚举
  • 其他说明:
    配置枚举包扫描的时候能提前注册使用注解枚举的缓存

声明通用枚举属性

方式一: 使用 @EnumValue 注解枚举属性

public enum GradeEnum {PRIMARY(1, "小学"),  SECONDORY(2, "中学"),  HIGH(3, "高中");GradeEnum(int code, String descp) {this.code = code;this.descp = descp;}@EnumValue//标记数据库存的值是codeprivate final int code;//。。。
}

方式二: 枚举属性,实现 IEnum 接口如下:

public enum AgeEnum implements IEnum<Integer> {ONE(1, "一岁"),TWO(2, "二岁"),THREE(3, "三岁");private int value;private String desc;@Overridepublic Integer getValue() {return this.value;}
}

实体属性使用枚举类型

public class User {/*** 名字* 数据库字段: name varchar(20)*/private String name;/*** 年龄,IEnum接口的枚举处理* 数据库字段:age INT(3)*/private AgeEnum age;/*** 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):* 数据库字段:grade INT(2)*/private GradeEnum grade;
}

配置扫描通用枚举

mybatis-plus:# 支持统配符 * 或者 ; 分割typeEnumsPackage: com.baomidou.springboot.entity.enums....

没想到,我的构思被MyBatis-Plus抄袭了。

一些说明

其实数据库有一种枚举字段类型,大家可以了解下,本文并没有介绍,个人不建议使用。

另外,MyEnumTypeHandler还有很多不足,最大的不足就是仍然不够通用。

假设现在系统新增InvitationStatusEnum:

DROP TABLE IF	EXISTS t_user;
CREATE TABLE `t_user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`name` varchar(50) DEFAULT '' COMMENT '姓名',`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',`rest_day` tinyint(1) DEFAULT 1 COMMENT '休息日',`invitation_status` varchar(50) DEFAULT '' COMMENT '面试状态',`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@Getter
public enum InvitationStatusEnum {WAIT_FOR_DEAL("wait_for_deal", "等待处理"),SUITABLE("suitable", "合适"),;@EnumValueprivate final String value;private final String desc;InvitationStatusEnum(String value, String desc) {this.value = value;this.desc = desc;}
}

你会发现又不行了。除非在@MappedTypes的属性中另外指定InvitationStatusEnum.class:

也就是说,作为通用组件的MyEnumTypeHandler还是无法避免被反复修改,不如MyBatis-Plus来得优雅。有兴趣的同学可以自行研究(意义不大)。

 

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

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

相关文章

Spring代理方式之静态、动态代理(JDK和CGlib动态代理)

目录 1、代理设计模式的概念 2、静态代理 3、动态代理&#xff08;JDK和CGlib动态代理&#xff09; 1. JDK动态代理是基于接口的代理&#xff08;Interface-based proxy&#xff09; 2. CGLIB代理是基于类的代理&#xff08;Class-based proxy&#xff09; ⭐比较&#x…

SpringBoot : ch11 整合RabbitMQ

前言 在当今的互联网时代&#xff0c;消息队列成为了构建高可靠、高性能系统的重要组件之一。RabbitMQ作为一个可靠、灵活的消息中间件&#xff0c;被广泛应用于各种分布式系统中。 本篇博客将介绍如何使用Spring Boot整合RabbitMQ&#xff0c;实现消息的发送和接收。通过这种…

真空工艺腔内潮湿有什么危害?

在半导体制程中&#xff0c;真空工艺腔被广泛使用。薄膜沉积&#xff0c;干法刻蚀&#xff0c;光刻&#xff0c;退火&#xff0c;离子注入等工序均需要在相应的真空腔室中完成相应制程。真空工艺腔在半导体制程中起着至关重要的作用&#xff0c;它能够提供一个高度控制的环境&a…

MT8390(Genio 700)安卓核心板_MTK联发科工业AI主板Linux开发板

MT8390 (Genio 700) 安卓核心板是一款高性能边缘人工智能物联网平台&#xff0c;尺寸仅为45452.2mm。该平台提供高度响应的边缘处理、先进的多媒体功能、各种传感器和连接选项&#xff0c;同时支持多任务操作系统。 Genio 700处理器拥有PS APU性能&#xff0c;高效的芯片内人工…

42. 接雨水(单调栈)

这道题我本来的做法是 使用两个指针定住两头第一个非零元素&#xff0c;然后计算中间有多少个0&#xff0c;就表示多少个“坑” 然后依次-1&#xff0c;再重复上述操作 但是遇到height[i]为上千的数字&#xff0c;就很蠢了。。。 并且超时 class Solution { public:int trap(v…

同旺科技 USB 转 RS-485 适配器

内附链接 1、USB 转 RS-485 适配器 基础版主要特性有&#xff1a;&#xff08;非隔离&#xff09; ● 支持USB 2.0/3.0接口&#xff0c;并兼容USB 1.1接口&#xff1b; ● 支持USB总线供电&#xff1b; ● 支持Windows系统驱动&#xff0c;包含WIN10 / WIN11系统32 / 64位…

SSL证书实惠品牌——JoySSL

随着互联网的普及和发展&#xff0c;网络安全问题日益严重。为了保护网站数据的安全&#xff0c;越来越多的网站开始使用SSL证书。JoySSL证书作为一款高性价比的SSL证书&#xff0c;受到了广泛的关注和好评。 目前市面上主流的证书基本上都是国外证书&#xff0c;也就是说你在验…

MSB3541 Files 的值“<<<<<<< HEAD”无效。路径中具有非法字符。

MSB3541 Files 的值“<<<<<<< HEAD”无效。路径中具有非法字符。 一般来说出现这个问题是因为使用git版本控制工具合并代码出现了问题&#xff0c;想要解决也很简单。 如图点击错误后定位到文件&#xff0c;发现也没有什么问题。 根据错误后边的提示&a…

Peter算法小课堂—高精度减法

给大家看个小视频高精度减法_哔哩哔哩_bilibili 基本思想 计算机模拟人类做竖式计算&#xff0c;从而得到正确答案 大家还记得小学时学的“减法竖式”吗&#xff1f;是不是这样 x-y问题 函数总览&#xff1a; 1.converts() 字符串转为高精度大数 2.le() 判断大小 3.sub() …

苹果mac屏幕投屏镜像工具AirServer2024

airserver 是什么软件&#xff1f;AirServer 是一款 Airplay Mac屏幕镜像应用&#xff0c;AirServer可以通过 mac 实时接收iPhone、iPad以及Android设备的实时屏幕画面。AirServer 可以将一个简单的大屏幕或投影仪变成一个通用的屏幕镜像接收器。在您的大屏幕上启用 AirServer …

环境监测传感器守护我们的地球

随着人类活动的不断增加&#xff0c;环境问题日益凸显。为了更好地保护我们的地球&#xff0c;环境监测成为了一项非常重要的任务。而在这个领域&#xff0c;传感器技术发挥着至关重要的作用。今天&#xff0c;我们就来聊聊WX-WQX12 环境监测传感器。 环境监测传感器是一种能够…

同旺科技 USB 转 RS-485 适配器 -- 隔离型(定制款)

内附链接 1、USB 转 RS-485 适配器 隔离版主要特性有&#xff1a; ● 支持USB 2.0/3.0接口&#xff0c;并兼容USB 1.1接口&#xff1b; ● 支持USB总线供电&#xff1b; ● 支持Windows系统驱动&#xff0c;包含WIN10 / WIN11 系统32 / 64位&#xff1b; ● 支持Windows …