非标准枚举类代码导致的事故

小夏 体育 更新 2024-01-28

阿里梅指南。

笔者参与了一次排查,最终结论与枚举类的规范有关,本文在这里总结一程,供大家学习习交流。 今天开发者给我讲了一个很奇怪的问题,说一个对象的 state 属性是一个枚举类,设置好对象的状态后,插入到数据库中,这个状态就消失了,凭空消失了,变成了一个空白的字符串。 这感觉很奇怪,我参与了整个故障排除过程,得到的结论与枚举类的规范有关,我在这里总结了这个过程,供大家一起学习和习。 问题**。

让我们先看看有问题的零件是什么样子的:

@overridepublic string insert(payrequest payrequest)
该方法非常简单,只需将传递的 payrequest 对象转换为 payrequestdo 对象并将其插入数据库即可。

PayRequest 和 PayRequestDo 是普通的 pojo 对象,没什么复杂的,只是 PayRequestDo 的状态被替换为字符串:

public class payrequest
public class payrequestdo
至于 PayConvertor Todo 方法,也很简单,它是一个属性副本:

public payrequestdo todo(payrequest payrequest)
开发中反复强调,带有输入参数的 payrequest 中的状态必须有一个值,并且写成死的,根本不能是空的,然后这些**也查过很多遍,mybatis 的 mapper xml 绝对没有问题,但是插入数据库就没有值了, 为什么?排查问题。

首先,简单地花一些时间去消除一些底层的问题,比如没有发布,或者部署了错误的版本等等,并确保服务器上运行的就是上面发布的,这是非常非常重要的,而且始终是检查问题时首先要做的(其实大部分问题都可以在这一步解决)。

以下是快速确定在线版本的两种方法: 解决方案 1:使用 git-commit-id-plugin m**en 插件启用 Spring Boot 的信息执行器:

配置开放执行器端点,开放端点需要注意数据安全,可以配置不同的管理端口或对敏感内容脱敏management.endpoints.web.exposure.include=info
一旦 mvn packge 构建完并使用 j**a -jar 启动,您就可以访问 localhost:8080 actuator info 来获取当前的 git 提交信息:

"id":"1234567""branch":"master"
使用此提交 ID 可确定它是哪个版本。

场景 2:如果您没有提前集成 git m**en 插件,或者没有打开信息端点。 您也可以将 fat jar 包拆下来并反编译以确定版本。 当然也有一些**可以dump**的解决方法,比如下面即将推出的arthas有一个jad命令,还有JDK自带的hsdb,也可以直接将内存中的类转储到本地磁盘,有兴趣的可以自己搜索。 --分界线--消除底层问题,然后我们分析问题出在哪里。 因为问题的表现是插入到数据库中的值丢失了,我们可以先看一下db的摘要日志分析

2023-11-14 21:06:04.221|paycenter|00|8||n|trace8423002774916857900o38o50|paycenter|pay_request_record|pay_request_record|insert|insert into pay_request (id, pay_id, pay_no, status) values (null, 'pay2023001', '20231114000000001', '')|0|servicehandler-11.2.60.188:20880-thread-8|0.1
从上面的摘要日志中的 SQL 可以看出,插入 SQL 中的 status 字段已经传递了''空白字符,这意味着在ORM框架中不会出现该问题,并且消除了XML中SQL语句不正确的问题。 因此,在插入数据库之前,业务方法中一定发生了问题。

接下来,我怀疑输入参数传递的payrequest中的status字段是没有价值的,我需要查看运行线程栈中payrequest对象的属性值,这个场景很适合使用arthas的watch方法,我的想法里面安装了一个arthas插件(名字叫王晓歌的arthas创意), 我们只需要右键点击插入并选择Arthas命令,选择watch子菜单,就可以得到watch命令,非常方便。

接下来,我们登录到服务器,切换到 admin 用户(arthas startup 要求你启动与 j**a 相同的用户),并输入我们刚刚复制的命令:

watch com.xxxxx.paycenter.service.repository.impl.payrequestrepositoryimpl insert '' -n 1 -x 3
然后我们等待方法执行到插入,然后我们可以观察 arthas watch 的输出

method=com.xxxxx.paycenter.service.repository.impl.payrequestrepositoryimpl$$enhancerbyspringcglib$$4f979dec.insert location=atexitts=2023-11-15 14:54:49; [cost=6.001557ms] result=@arraylist[@object[@payrequest[serialversionuid=@long[1],payid=@string[pay2023001],payno=@string[20231114000000001],status=@status[init],@string[20231114000000001],null,
可以清楚地看到,这里添加参数时,status 属性还是有一个值的:statusinit

接下来,当我们查看映射器的写入数据时,status 属性仍然存在,我们还使用 arthas watch 命令:

watch com.xxxxx.paycenter.infrastructure.dal.mapper.paymapper insert '' -n 1 -x 3
然后等待方法执行到映射器的插入中,并观察 arthas 观看的内容:

affect(class count: 2 , method count: 1) cost in 289 ms, listenerid: 10method=com.sun.proxy.$proxy153.insert location=atexitts=2023-11-15 14:51:36; [cost=3.933553ms] result=@arraylist[@object[@payrequestdo[id=null,payid=@string[pay2023002],payno=@string[20231114000000002],status=@string,@integer[1],null,
正如你所看到的,很明显,当数据库入时,状态已经变成了空!

插入时有参数,但写入数据库时就消失了,这意味着唯一的问题是中间的对象转换方法payconvertor todo。 使用 arthas 观察 todo 的输入和输出参数:

watch com.xxxxx.paycenter.core.convertor.payconvertor todo '' -n 1 -x 3
输出:

method=com.xxxxx.paycenter.core.convertor.payconvertor.todo location=atexitts=2023-11-15 15:01:35; [cost=0.432887ms] result=@arraylist[@object[@payrequest[serialversionuid=@long[1],payid=@string[pay2023003],payno=@string[20231114000000003],status=@status[init],@payrequestdo[id=null,payid=@string[pay2023003],payno=@string[20231114000000003],status=@string,null,
查看输出,问题确实发生在 todo 内部,并且 status 属性在数据转换后消失了。

确切地说,以下行 ** 丢失了该属性:

payrequestdo.setstatus(payrequest.getstatus().getcode())
找出原因。

到目前为止,我们已经发现原因可能是 status 属性后面的枚举类 status 在 getcode 时返回空。 状态的**如下:

public enum status

public string getcode()

public void setcode(string code)

public string getdesc()

public void setdesc(string desc)

看着枚举类属性的 setter 方法,我不禁思考:为什么枚举类的属性要提供 setter 方法?

一般来说,枚举类的属性必须设置为最终的关键字修饰,不能提供 setter 方法。 试想一下,如果我通过二传手用以下方式交换失败和成功码,这个**能继续快乐地玩吗?

status.failed.setcode("success");

status.success.setcode("failed");

显然,此处提供的 setter 调用直接破坏了枚举类,因此最好的办法是将 final 添加到枚举类属性中。

接下来传递手表状态''参数''可以打印对象内部的状态,结果输出进一步证实了我的怀疑,即状态枚举类中枚举的 code 属性已成为空白字符串

watch com.xxxxx.paycenter.core.enums.status getcode '' -n 1 -x 3

method=com.xxxxx.paycenter.core.enums.status.getcode location=atexitts=2023-11-15 15:05:36; [cost=0.005638ms] result=@arraylist[@status[init=@status[init=@status[init],success=@status[success],failed=@status[failed],code=@string,desc=@string,name=@string[init],ordinal=@integer[0],

根源。

直接原因基本已经找到了,然后我们需要确切知道 **中调用的枚举类的 setcode 方法是什么,为了什么需求,因为我在整个项目中没有找到显示的调用,所以修改枚举的 setcode,添加一些 **,以便在调用 setcode 时打印调用栈:

public void setcode(string code) catch (exception e) ,after: {", this.code, code, e);this.code = code;
有朋友反映抛出异常看栈太难看了,这也提供了一个不需要抛出异常的解决方案:

public void setcode(string code) ,after: {", this.code, code, formatstacktrace(stacktrace));this.code = code;

public static string formatstacktrace(stacktraceelement stacktrace)return stringbuilder.tostring();

添加**发布后,很快就从堆栈中打印出来了:

这是 podam 引起的问题,第三方库(发起者还是我介绍的库),这个库的作用是传递一个类对象,解析出它的属性,并赋值,简单来说,就是按照类生成随机对象的随机属性,测试工具会用到这个函数, 这个库在解析枚举类时可能没有实现好,导致枚举 setter 方法被反射调用,最终导致问题。改进。

我们排查了数据库中缺少插入属性的问题,最后发现问题的原因是一个非标准枚举类写入引起的问题。 首先是写**或者注意规范,最好在当地安装一些扫描工具,比如声纳,发现风险必须按照建议尽快修复。 二是第三方库 podam 在枚举的实现上还存在问题,这个 bug 需要尽快修复。 最后,我也在联系**扫描团队,看看能不能把这样的案例实现到平台检测能力中,在提交的时候扫描问题,这样就可以把问题扼杀在摇篮里。 好在联系了**扫描的一位同事,我们公司确实有这样的检测,但是没有加入**扫描规则,在我的建议下,将这条规则加入到**扫描中。

欢迎加入【阿里云开发者***》读者群】

这是“阿里云开发者”读者的特殊交流空间。

相似文章

    出国留学是人生的经历,还是一生的赌博?

    随着社会经济的发展和人们对多元化教育的追求,越来越多的家庭选择将孩子送到国外接受国际教育。但是,出国留学好吗?我们从不同的角度分析这个话题,以帮助您理清思路并做出明智的决定。首先,我们来谈谈出国留学的优缺点 开阔视野,丰富人生阅历,在未来职场竞争中更具优势 出国留学让学生接触到更广阔的视野,了解不同...

    Crush 无声独奏

    暗恋的影响和后果。暗恋不仅会影响个人的情绪状态,还会影响个人的生活 习和工作。这个话题可以讨论暗恋对个人生活 习和工作的影响,以及暗恋可能造成的一些后果,如焦虑 抑郁和其他心理问题。同时,也可以处理这些问题,以减轻暗恋对个人的负面影响。在人生的情感舞台上,暗恋无疑是最复杂 最微妙的情感表达。它既是情...

    没有预约的渴望

    风轻轻扫过记忆,留下淡淡的你痕迹,偶尔想起,心里还是微微痛,虽然你我是单方面的感情,却永远留在了我的记忆里。就像大家一样,他们匆匆忙忙地走在自己的路上,不停地走着,不经意间,在某个十字路口相遇,于是,温柔的问候,淡淡的问候,然后说再见,却再也不见面了!让时间流逝,让记忆在那一刻徘徊,这一刻是那么的安...

    模特之间的较量

    汽车市场的竞争比以往任何时候都更加激烈,车企推出新车型的速度也越来越快。今天,我们来比较一下三款热门机型 小鹏G Omenda和Supai。这三种车型在各自的细分市场中都具有很强的竞争力,那么让我们来看看它们之间的区别和优缺点。首先是小鹏G,这款售价为, 到 万元之间的SUV车型属于小鹏汽车品牌的国...

    婚姻的沉沦与重生

    婚姻,是生命的注脚,还是伤痕累累的缺口?每个人可能有不同的定义,但正如莎士比亚所说,匆忙的婚姻很少是幸福的,有时甚至是人生的灾难。曾经在国企职工 工作稳定的梁湘萍,在婚姻中遭受了前所未有的沉重打击。刚步入婚姻殿堂时,她以为自己已经幸福了,但痴情终究是负的,婚姻生活变成了锅碗瓢盆和丈夫出轨的痛苦的琐碎...