posthandle中获取response返回的值_求你了,不要再在对外
接⼝中使⽤枚举类型了
最近,我们的线上环境出现了⼀个问题,线上代码在执⾏过程中抛出了⼀个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容:
java.lang.IllegalArgumentException: No enum constant com.a.a.c.AType.P_M
⼤概就是以上的内容,看起来还是很简单的,提⽰的错误信息就是在AType这个枚举类中没有到P_M这个枚举项。
于是经过排查,我们发现,在线上开始有这个异常之前,该应⽤依赖的⼀个下游系统有发布,⽽发布过程中是⼀个API包发⽣了变化,主要变化内容是在⼀个RPC接⼝的Response返回值类中的⼀个枚举参数AType中增加了P_M这个枚举项。
但是下游系统发布时,并未通知到我们负责的这个系统进⾏升级,所以就报错了。
我们来分析下为什么会发⽣这样的情况。
问题重现
⾸先,下游系统A提供了⼀个⼆⽅库的某⼀个接⼝的返回值中有⼀个参数类型是枚举类型。
⼀⽅库指的是本项⽬中的依赖
⼆⽅库指的是公司内部其他项⽬提供的依赖
三⽅库指的是其他组织、公司等来⾃第三⽅的依赖
然后B系统依赖了这个⼆⽅库,并且会通过RPC远程调⽤的⽅式调⽤AFacadeService的doSth⽅法。
这时候,如果A和B系统依赖的都是同⼀个⼆⽅库的话,两者使⽤到的枚举AType会是同⼀个类,⾥⾯的枚举项也都是⼀致的,这种情况不会有什么问题。
但是,如果有⼀天,这个⼆⽅库做了升级,在AType这个枚举类中增加了⼀个新的枚举项P_M,这时候只有系统A做了升级,但是系统B并没有做升级。
那么A系统依赖的的AType就是这样的:
⽽B系统依赖的AType则是这样的:
这种情况下,在B系统通过RPC调⽤A系统的时候,如果A系统返回的AResponse中的aType的类型为
新增的P_M时候,B系统就会⽆法解析。⼀般在这种时候,RPC框架就会发⽣反序列化异常。导致程序被中断。
原理分析
这个问题的现象我们分析清楚了,那么再来看下原理是怎样的,为什么出现这样的异常呢。
其实这个原理也不难,这类RPC框架⼤多数会采⽤JSON的格式进⾏数据传输,也就是客户端会将返回值序列化成JSON字符串,⽽服务端会再将JSON字符串反序列化成⼀个Java对象。
⽽JSON在反序列化的过程中,对于⼀个枚举类型,会尝试调⽤对应的枚举类的valueOf⽅法来获取到对应的枚举。
⽽我们查看枚举类的valueOf⽅法的实现时,就可以发现,如果从枚举类中不到对应的枚举项的时候,就会抛出IllegalArgumentException:
public static > T valueOf(Class enumType, String name) {    T result = umConstantDirectory().get(name);    if (result != null)        return result 关于这个问题,其实在《阿⾥巴巴Java开发⼿册》中也有类似的约定:
这⾥⾯规定"对于⼆⽅库的参数可以使⽤枚举,但是返回值不允许使⽤枚举"。这背后的思考就是本⽂上⾯提到的内容。
json值的类型有哪些扩展思考
为什么参数中可以有枚举?
不知道⼤家有没有想过这个问题,其实这个就和⼆⽅库的职责有点关系了。
⼀般情况下,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注解表明这个字符串字段的取值从那个枚举中获取。
对于像阿⾥这种⽐较庞⼤的互联⽹公司,随便提供出去的⼀个接⼝,可能有上百个调⽤⽅,⽽接⼝升级也是常态,我们根本做不到每次⼆⽅库升级之后要求所有调⽤者跟着⼀起升级,这是完全不现实的,并且对于有些调⽤者来说,他⽤不到新特性,完全没必要做升级。
还有⼀种看起来⽐较特殊,但是实际上⽐较常见的情况,就是有的时候⼀个接⼝的声明在A包中,⽽⼀些枚举常量定义在B包中,⽐较常见
的就是阿⾥的交易相关的信息,订单分很多层次,每次引⼊⼀个包的同时都需要引⼊⼏⼗个包。
对于调⽤者来说,我肯定是不希望我的系统引⼊太多的依赖的,⼀⽅⾯依赖多了会导致应⽤的编译过程很慢,并且很容易出现依赖冲突问题。
所以,在调⽤下游接⼝的时候,如果参数中字段的类型是枚举的话,那我没办法,必须得依赖他的⼆⽅库。但是如果不是枚举,只是⼀个字符串,那我就可以选择不依赖。
所以,我们在定义接⼝的时候,会尽量避免使⽤枚举这种强类型。规范中规定在返回值中不允许使⽤,⽽我⾃⼰要求更⾼,就是即使在接⼝的⼊参中我也很少使⽤。
最后,我只是不建议在对外提供的接⼝的出⼊参中使⽤枚举,并不是说彻底不要⽤枚举,我之前很多⽂章也提到过,枚举有很多好处,我在代码中也经常使⽤。所以,切不可因噎废⾷。
当然,⽂中的观点仅代表我个⼈,具体是是不是适⽤其他⼈,其他场景或者其他公司的实践,需要读者们⾃⾏分辨下,建议⼤家在使⽤的时候可以多思考⼀下。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。