一、前言
背景:线上告警群,收到告警:BigMoney java.lang.ArithmeticException
业务流程:这块是通用方法,调用中台接口。
核心作用:把“批次单(BatchOrder)在支付中台已冻结的总金额”里,按单笔拆出一部分做“单笔解冻”,为后续该笔付款走“正常单笔付款预下单/支付流程”做资金释放/衔接。
直接通过异常日志,可以知道:
- 序列化
org.joda.money.Money时 FastJSON 的 ASM 序列化器会调用getAmountMajorInt(),其内部使用BigDecimal.intValueExact() - 金额部分超过
Integer.MAX_VALUE时就会抛出ArithmeticException: Overflow
再结合上下文日志发现是在处理 53亿 VND(越南盾)的时候出现了异常:
- 在 Java 里,
Integer.MAX_VALUE的值是 2147483647(即2^31 - 1),21亿 - 53亿 远超了 21亿
小结:
1、org.joda.money.Money内部有定义getAmountMajorInt()方法
2、FastJSON 生成的 ASM 序列化器会按属性列表逐个调用所有公开的 getter(按名称/类型排序),执行到 getAmountMajorInt(),然后整型溢出。
3、这种日志打印万万不能影响支付链路。
二、实验复现
测试代码如下:
@Slf4j
public class MockTest extends TestBase {
public void test() {
// {"amount":5370000000,"currency":"VND"}
BigDecimal amount = BigDecimal.valueOf(5370000000L);
CurrencyUnit currencyUnit = CurrencyUnit.VND;
Money money = Money.of(currencyUnit, amount);
log.info("result: "+ JSON.toJSONString(money));
System.out.println("====> success");
}
}
运行后就会报错:
三、问题解决
解决这个问题可以分为两个方向:
1、改org.joda.money.Money,自定义 Money 的序列化 / 忽略问题 getter
@JSONField(serialize = false)
public int getAmountMajorInt() {
return getAmountMajor().intValueExact();
}
2、改 JSON 输出方式:升级到新版 FastJSON 2.x 并用 @JSONField(serializeUsing=...) 定制 Money 序列化,或切到 Jackson + joda-money module。
🌰举例子如下:以 Jackson 为例
- 依赖:
<dependencies>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda-money</artifactId>
</dependency>
</dependencies>
- 自定义日志打印:
public class LogConverter {
private static final ObjectMapper objectMapper = newObjectMapper();
// 配置
public static ObjectMapper newObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaMoneyModule());
return objectMapper;
}
// 调用这个方法来打日志
public static String toJsonString(Object original) {
return objectMapper.writeValueAsString(original);
}
}