在一次线上支付系统的告警中,我们收到了BigMoney java.lang.ArithmeticException的异常提示。该异常发生在处理批量付款流程时,日志打印操作意外中断了支付链路。
业务流程与问题背景
该模块负责调用支付中台接口,核心作用是将批次单在支付中台已冻结的总金额,按单笔拆解并进行“单笔解冻”,以便后续走正常单笔付款流程。然而,在日志打印序列化过程中,出现了异常。
异常根本原因
通过异常日志分析,发现序列化org.joda.money.Money对象时,FastJSON的ASM序列化器会调用getAmountMajorInt()方法。该方法内部使用BigDecimal.intValueExact()进行转换,当金额部分超过Integer.MAX_VALUE(即21亿)时,就会抛出ArithmeticException: Overflow。
结合上下文日志,问题出现在处理53亿越南盾(VND)的场景中:
- 在Java中,
Integer.MAX_VALUE的值为2147483647(约21亿)。
- 53亿远超此限制,导致整型溢出。
问题小结
org.joda.money.Money类定义了getAmountMajorInt()公开方法。
- FastJSON生成的序列化器会遍历所有getter方法,执行到
getAmountMajorInt()时触发溢出。
- 这类日志打印操作绝不能影响核心支付链路的稳定性。
实验复现
通过以下测试代码可以复现该问题:
@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");
}
}
运行后,控制台会抛出ArithmeticException异常,与线上情况一致。
解决方案
解决该问题可以从两个方向入手:
1. 修改Money类的序列化行为
在org.joda.money.Money类中,为getAmountMajorInt()方法添加FastJSON注解,忽略其序列化:
@JSONField(serialize = false)
public int getAmountMajorInt() {
return getAmountMajor().intValueExact();
}
2. 更换JSON输出方式
升级到FastJSON 2.x版本并使用@JSONField(serializeUsing=...)定制序列化器,或者切换到Jackson并配合joda-money模块。以下是使用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) throws JsonProcessingException {
return objectMapper.writeValueAsString(original);
}
}
使用LogConverter.toJsonString()方法替代原有的JSON序列化,可以避免整型溢出问题,确保支付系统日志打印的稳定性。
|