求你了,不要再在对外接口中使用枚举类型了!
最近,我们的线上环境出现了一个问题,线上代码在执行过程中抛出了一个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容:
java.lang.IllegalArgumentException:
No enum constant com.a.b.f.m.a.c.AType.P_M
大概就是以上的内容,看起来还是很简单的,提示的错误信息就是在AType这个枚举类中没有找到P_M这个枚举项。
于是经过排查,我们发现,在线上开始有这个异常之前,该应用依赖的一个下游系统有发布,而发布过程中是一个API包发生了变化,主要变化内容是在一个RPC接口的Response返回值类中的一个枚举参数AType中增加了P_M这个枚举项。
但是下游系统发布时,并未通知到我们负责的这个系统进行升级,所以就报错了。
我们来分析下为什么会发生这样的情况。
问题重现
首先,下游系统A提供了一个二方库的某一个接口的返回值中有一个参数类型是枚举类型。
一方库指的是本项目中的依赖
二方库指的是公司内部其他项目提供的依赖
三方库指的是其他组织、公司等来自第三方的依赖
public interface AFacadeService {
public AResponse doSth(ARequest aRequest);
}
public Class AResponse{
private Boolean success;
private AType aType;
}
public enum AType{
P_T,
A_B
}然后B系统依赖了这个二方库,并且会通过RPC远程调用的方式调用AFacadeService的doSth方法。
public class BService {
@Autowired
AFacadeService aFacadeService;
public void doSth(){
ARequest aRequest = new ARequest();
AResponse aResponse = aFacadeService.doSth(aRequest);
AType aType = aResponse.getAType();
}
}
这时候,如果A和B系统依赖的都是同一个二方库的话,两者使用到的枚举AType会是同一个类,里面的枚举项也都是一致的,这种情况不会有什么问题。
但是,如果有一天,这个二方库做了升级,在AType这个枚举类中增加了一个新的枚举项P_M,这时候只有系统A做了升级,但是系统B并没有做升级。
那么A系统依赖的的AType就是这样的:
public enum AType{
P_T,
A_B,
P_M
}而B系统依赖的AType则是这样的:
public enum AType{
P_T,
A_B
}
这种情况下,在B系统通过RPC调用A系统的时候,如果A系统返回的AResponse中的aType的类型为新增的P_M时候,B系统就会无法解析。一般在这种时候,RPC框架就会发生反序列化异常。导致程序被中断。
原理分析
这个问题的现象我们分析清楚了,那么再来看下原理是怎样的,为什么出现这样的异常呢。
其实这个原理也不难,这类RPC框架大多数会采用JSON的格式进行数据传输,也就是客户端会将返回值序列化成JSON字符串,而服务端会再将JSON字符串反序列化成一个Java对象。
而JSON在反序列化的过程中,对于一个枚举类型,会尝试调用对应的枚举类的valueOf方法来获取到对应的枚举。
而我们查看枚举类的valueOf方法的实现时,就可以发现,如果从枚举类中找不到对应的枚举项的时候,就会抛出IllegalArgumentException:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
关于这个问题,其实在《阿里巴巴Java开发手册》中也有类似的约定:

这里面规定"对于二方库的参数可以使用枚举,但是返回值不允许使用枚举"。这背后的思考就是本文上面提到的内容。
扩展思考
为什么参数中可以有枚举?
不知道大家有没有想过这个问题,其实这个就和二方库的职责有点关系了。
一般情况下,A系统想要提供一个远程接口给别人调用的时候,就会定义一个二方库,告诉其调用方如何构造参数,调用哪个接口。
而这个二方库的调用方会根据其中定义的内容来进行调用。而参数的构造过程是由B系统完成的,如果B系统使用到的是一个旧的二方库,使用到的枚举自然是已有的一些,新增的就不会被用到,所以这样也不会出现问题。
比如前面的例子,B系统在调用A系统的时候,构造参数的时候使用到AType的时候就只有P_T和A_B两个选项,虽然A系统已经支持P_M了,但是B系统并没有使用到。
如果B系统想要使用P_M,那么就需要对该二方库进行升级。
但是,返回值就不一样了,返回值并不受客户端控制,服务端返回什么内容是根据他自己依赖的二方库决定的。
但是,其实相比较于手册中的规定,我更加倾向于,在RPC的接口中入参和出参都不要使用枚举。
一般,我们要使用枚举都是有几个考虑:
-
1、枚举严格控制下游系统的传入内容,避免非法字符。
-
2、方便下游系统知道都可以传哪些值,不容易出错。
不可否认,使用枚举确实有一些好处,但是我不建议使用主要有以下原因:
-
1、如果二方库升级,并且删除了一个枚举中的部分枚举项,那么入参中使用枚举也会出现问题,调用方将无法识别该枚举项。
-
2、有的时候,上下游系统有多个,如C系统通过B系统间接调用A系统,A系统的参数是由C系统传过来的,B系统只是做了一个参数的转换与组装。这种情况下,一旦A系统的二方库升级,那么B和C都要同时升级,任何一个不升级都将无法兼容。
我其实建议大家在接口中使用字符串代替枚举,相比较于枚举这种强类型,字符串算是一种弱类型。
如果使用字符串代替RPC接口中的枚举,那么就可以避免上面我们提到的两个问题,上游系统只需要传递字符串就行了,而具体的值的合法性,只需要在A系统内自己进行校验就可以了。
为了方便调用者使用,可以使用javadoc的@see注解表明这个字符串字段的取值从那个枚举中获取。
public Class AResponse{
private Boolean success;
/**
* @see AType
*/
private String aType;
}
对于像阿里这种比较庞大的互联网公司,随便提供出去的一个接口,可能有上百个调用方,而接口升级也是常态,我们根本做不到每次二方库升级之后要求所有调用者跟着一起升级,这是完全不现实的,并且对于有些调用者来说,他用不到新特性,完全没必要做升级。
还有一种看起来比较特殊,但是实际上比较常见的情况,就是有的时候一个接口的声明在A包中,而一些枚举常量定义在B包中,比较常见的就是阿里的交易相关的信息,订单分很多层次,每次引入一个包的同时都需要引入几十个包。
对于调用者来说,我肯定是不希望我的系统引入太多的依赖的,一方面依赖多了会导致应用的编译过程很慢,并且很容易出现依赖冲突问题。
所以,在调用下游接口的时候,如果参数中字段的类型是枚举的话,那我没办法,必须得依赖他的二方库。但是如果不是枚举,只是一个字符串,那我就可以选择不依赖。
所以,我们在定义接口的时候,会尽量避免使用枚举这种强类型。规范中规定在返回值中不允许使用,而我自己要求更高,就是即使在接口的入参中我也很少使用。
最后,我只是不建议在对外提供的接口的出入参中使用枚举,并不是说彻底不要用枚举,我之前很多文章也提到过,枚举有很多好处,我在代码中也经常使用。所以,切不可因噎废食。
当然,文中的观点仅代表我个人,具体是是不是适用其他人,其他场景或者其他公司的实践,需要读者们自行分辨下,建议大家在使用的时候可以多思考一下。
相关文章:
求你了,不要再在对外接口中使用枚举类型了!
最近,我们的线上环境出现了一个问题,线上代码在执行过程中抛出了一个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容: java.lang.IllegalArgumentException: No enum constant com.a.b.f.m.a.c.A…...
Java开发学习(四十六)----MyBatisPlus新增语句之id生成策略控制及其简化配置
在前面有一篇博客:Java开发学习(四十一)----MyBatisPlus标准数据层(增删查改分页)开发,我们在新增的时候留了一个问题,就是新增成功后,主键ID是一个很长串的内容。 我们更想要的是按照数据库表字段进行自增…...
章鱼哥听歌
uboot环境变量 以下所有的命令,都在串口工具进行执行 ubifsmount- mount UBIFS volume ubifsumount- unmount UBIFS volume ums - Use the UMS [USB Mass Storage] usb - USB sub-system usbboot - boot from USB device version - print monit…...
软件测试电商项目实战(写进简历没问题)
前言 说实话,在找项目的过程中,我下载过(甚至付费下载过)N多个项目、联系过很多项目的作者,但是绝大部分项目,在我看来,并不适合你拿来练习,它们或多或少都存在着“问题”ÿ…...
算法导论—分治法思想、动态规划思想、贪心思想
算法导论—分治法思想、动态规划思想、贪心思想分治法的思想:动态规划:贪心算法:贪心算法求解问题的条件:设计贪心算法的步骤:分治法的思想: 将原问题分解为几个规模较小但类似于原问题的子问题࿰…...
Spring-Data-Jpa实现继承实体类
写在前面:从2018年底开始学习SpringBoot,也用SpringBoot写过一些项目。现在对学习Springboot的一些知识总结记录一下。如果你也在学习SpringBoot,可以关注我,一起学习,一起进步。 相关文章: 【Springboot系…...
多线程环境下的伪共享
今天和大家聊一聊伪共享 1.什么是伪共享? 缓存一致性协议在计算机中针对的最小单元:缓存行,每个缓存行的大小是64字节,一串连续的64字节数据都会存储到缓存行中。 假设数据A和数据B在同一缓存行中,CPU1修改了数据A&am…...
【Taylor and Francis】1/2区云计算、物联网、机器学习类,SCIEEI双检,审稿友好
机器学习类 【期刊简介】IF:6.5-7.0,JCR1/2区,中科院3区 【检索情况】SCIE&EI双检 【参考周期】2-3个月左右录用 【征稿领域】面向制造业云计算物联网应用的机器学习方法 【截稿日期】10篇版面 毕业必看-快刊 计算机科学类…...
CleanMyMac X4.12新版本下载及功能介绍
CleanMyMac X2023最新版终于迎来了又4.12,重新设计了 UI 元素,华丽的现代化风格显露无余。如今的CleanMyMac,早已不是单纯的系统清理工具。在逐渐融入系统优化、软件管理、文件管理等功能后,逐渐趋近于macOS的系统管家,…...
大数据技术架构(组件)26——Spark:Shuffle
2.1.6、Shuffle2.1.6.0 Shuffle Read And WriteMR框架中涉及到一个重要的流程就是shuffle,由于shuffle涉及到磁盘IO和网络IO,所以shuffle的性能直接影响着整个作业的性能。Spark其本质也是一种MR框架,所以也有自己的shuffle实现。但是和MR中的shuffle流程…...
关于Zebec生态的改进提案,即将上线的 Nautilus 链
概括 在最初作为 Solana 原生应用程序推出一年后,Zebec 团队已经能够通过在 BNB和NEAR区块链上成功部署来扩大其产品的范围。 凭借继续向尽可能多的公司/协议/基金提供薪资工具和基础设施的雄心勃勃的计划,我们决定采用最终将使 Zebec生态系统及其核心…...
Python数据可视化(三)(pyecharts)
分享一些python-pyecharts作图小技巧,用于展示汇报。 一、特点 任何元素皆可配置pyecharts只支持python原生的数据类型,包括int,float,str,bool,dict,list动态展示,炫酷的效果,给人视觉冲击力 # 安装 pip install pyecharts fr…...
【Redis面试指南】
Redis面试指南 Redis是一个开源的、基于内存的、高性能的键值对存储系统,它可以用于存储非常大量的数据,并且可以在短时间内获取数据。Redis的性能被广泛用于Web应用程序的缓存层,以提高应用程序的性能和可用性。Redis的面试是一个比较复杂的…...
大数据技术之Hadoop(生产调优手册)
第1章 HDFS—核心参数 1.1 NameNode内存生产配置 1)NameNode内存计算 每个文件块大概占用150byte,一台服务器128G内存为例,能存储多少文件块呢? 128 * 1024 * 1024 * 1024 / 150Byte ≈ 9.1亿 G MB KB Byte 2)Hadoop…...
「Vue源码学习」常见的 Vue 源码面试题,看完可以说 “精通Vue” 了吗?
Vue源码面试题一、行时(Runtime) 编译器(Compiler) vs. 只包含运行时(Runtime-only)webpackRollupBrowserify二、Vue 的初始化过程(面试关问:new Vue(options) 发生了什么࿱…...
FreeModbus RTU 移植指南
FreeModbus 简介 FreeModbus 是一个免费的软件协议栈,实现了 Modbus 从机功能: 纯 C 语言支持 Modbus RTU/ASCII支持 Modbus TCP 本文介绍 Modbus RTU 移植。 移植环境: 裸机Keil MDK 编译器Cortex-M3 内核芯片(LPC1778/88&…...
《唐诗三百首》数据源网络下载
2023年的 元宵之夜,这场以“长安”为主题的音乐会火了!在抖音,超过2300万人次观看了直播,在线同赏唐诗与交响乐的融合。许多网友惊呼,上学时那些害怕背诵的诗句,原来还可以有这么美的表达这场近80分钟的音乐…...
(深度学习快速入门)第五章第一节2:GAN经典案例之MNIST手写数字生成
获取pdf:密码7281 文章目录一:数据集介绍二:GAN简介(1)简介(2)损失函数三:代码编写(1)参数及数据预处理(2)生成器与判别器模型&#x…...
雁过留痕,竟是病毒的痕迹?
凌恩生物全新升级宏病毒组分析流程;聚焦DNA,RNA病毒组研究热点;高灵敏度检测vOTUs;多软件整合,精准鉴定病毒序列;直击地化循环关键环节,助力宏病毒组科研成功!期刊:Micro…...
Linux基本功系列之sort命令实战
文章目录前言一. sort命令介绍二. 语法格式及常用选项三. 参考案例3.1 按照文本默认排序3.2 忽略相同的行3.3 按数字大小进行排序3.4 检查文件是否已经按照顺序排序3.5 将第3列按照数字大小进行排序3.6 将排序结果输出到文件四. 探讨 -k的高级用法总结前言 大家好,…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
