在spring cloud 2023.0.2, seata2.0版本中会遇到如下问题:
业务逻辑抛出自定义异常后,上层捕获不到业务原始异常信息,只能看到类似:
java.lang.RuntimeException: try to proceed invocation error
at io.seata.spring.annotation.AdapterInvocationWrapper.proceed(AdapterInvocationWrapper.java:59)
at io.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler$2.execute(GlobalTransactionalInterceptorHandler.java:200)
at io.seata.tm.api.TransactionalTemplate.execute(TransactionalTemplate.java:128)
at io.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler.handleGlobalTransaction(GlobalTransactionalInterceptorHandler.java:197)
at io.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler.doInvoke(GlobalTransactionalInterceptorHandler.java:166)
at io.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler.invoke(AbstractProxyInvocationHandler.java:35)
at io.seata.spring.annotation.AdapterSpringSeataInterceptor.invoke(AdapterSpringSeataInterceptor.java:45)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)即便你在业务层抛出了具体的异常(如 BusinessException),Seata 在拦截并回滚后 只包装成了 RuntimeException,导致原始异常信息丢失,这给统一异常处理、返回友好错误提示带来了很大困扰。
问题描述与重现场景
在一个常见的 Spring Cloud + Seata 分布式事务场景里:
@GlobalTransactional
public void createOrder(...) {
// 业务检查
if(stockNotEnough()) {
throw new BusinessException("库存不足");
}
// 其他业务...
}
运行后,服务回滚成功,但输出弱化成:
java.lang.RuntimeException: try to proceed invocation error
而不是看到原始的 "库存不足" 异常提示或堆栈。此时前端只会拿到 RuntimeException 信息。
原因分析
这是 Seata 2.0 特有的问题:
Seata AOP/拦截器机制把内部异常封装得过早
Seata 通过内部拦截器代理业务方法:
AdapterInvocationWrapper.proceed(...)
这是 Seata 在调用目标业务方法时的一个 wrapper。在捕获业务异常后,它会统一抛出:
RuntimeException: try to proceed invocation error
而原始异常被作为 Cause 记录,但 上层捕获只有 RuntimeException,导致业务层异常被“吞掉”。
社区确认:这是 Seata 2.0 的 Bug
在 Seata 官方的 Issues 中,有相关讨论:https://github.com/apache/incubator-seata/issues/6622
社区确定后续版本将修复这个问题,也就是说这是一个 已知缺陷,而不是你写代码的问题。
解决方案
降级版本至seata1.8,实测可行