# Dubbo 自定义异常 + fastjson2 序列化踩坑记录
## 环境
- Spring Boot 3.0.2
- Spring Cloud 2022.0.0
- Spring Cloud Alibaba 2022.0.0.0
- Dubbo 3.2.4
- fastjson2 2.0.34 (Dubbo 默认序列化)
---
## 坑一:Nacos artifactId 命名问题
**现象:** pom.xml 中 dubbo、nacos 依赖报版本缺失
```
'dependencies.dependency.version' for com.alibaba.cloud:spring-cloud-alibaba-nacos-config:jar is missing.
```
**原因:** Spring Cloud Alibaba 2022.0.0.0 中的 Nacos 组件 artifactId 已改为带 `starter-` 前缀的命名,旧版命名找不到。
**修复:**
```xml
<!-- 错误 -->
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
<!-- 正确 -->
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
```
---
## 坑二:`<relativePath/>` 导致父 POM 找不到
**现象:** 子模块找不到 parent,所有 dependencyManagement 管理的版本全部失效
**原因:** `<relativePath/>` 为空时 Maven 去远程仓库找父 POM,本地模块不在远程仓库
**修复:**
```xml
<!-- 错误 -->
<relativePath/> <!-- lookup parent from repository -->
<!-- 正确 -->
<relativePath>../pom.xml</relativePath>
```
---
## 坑三:fastjson2 autoType 拦截异常序列化
**现象:** Dubbo provider 抛出 `busException`,consumer 端收到 `RpcException`,异常处理器永远拿不到原始自定义异常
**完整调用链路:**
```
brandController (consumer)
→ @DubboReference brandService.findById(-1)
→ [Dubbo RPC]
→ brandServiceImpl.findById(-1)
→ throw busException(601, "参数异常")
← Dubbo 用 fastjson2 序列化异常对象
← fastjson2 检查 autoType 白名单 → busException 不在名单内 → 拦截
← consumer 收到 RpcException(不是 busException)
← @ExceptionHandler(busException.class) 匹配不到
← @ExceptionHandler(Exception.class) 兜底,e.getMessage() = 巨大堆栈字符串
```
**核心错误信息:**
```
com.alibaba.fastjson2.JSONException: auotype not support : com.cyx.exception.busException
```
**原因:** fastjson2 自带 autoType 安全机制,默认阻止非白名单类的反序列化,防止反序列化漏洞攻击。自定义异常类和 JDK 异常类默认都不在白名单里。
**尝试过的方案:**
1. YAML 配置白名单 — **无效**,fastjson2 不读 Spring YAML 的 autoType 配置
2. Java 代码 `JSONFactory.addAutoTypeAccept()` — **无效**,因为 Dubbo 用的是 JSONB 格式,和 `JSONFactory` 走的不是同一个 ObjectReaderProvider
**最终解决:** 切换 Dubbo 序列化为 **hessian2**(hessian2 原生支持 Java Throwable 序列化,没有 autoType 限制)
```yaml
dubbo:
application:
prefer-serialization: hessian2 # 替代默认的 fastjson2,hessian2
```
注意:**provider 和 consumer 两边都要加,两边都要重启。**
---
## 坑四:@ComponentScan 未覆盖共享包
**现象:** 异常处理器配置好了但不生效,所有异常返回的是控制台报错而非 JSON
**原因:** `@RestControllerAdvice` 在 `com.cyx.exception` 包,`@SpringBootApplication` 在 `com.cyx.shopping_manager_api` 包,默认只扫描自身及子包,`com.cyx.exception` 不在扫描范围内
**修复:**
```java
@ComponentScan("com.cyx") // 覆盖所有共享包
@SpringBootApplication
public class ShoppingManagerApiApplication { ... }
```
或通过 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 注册:
```
com.cyx.exception.globalExceptionHandler
```
**注意:** 目录名是 `META-INF` 不是 `META-INFO`,拼错不生效。
---
## 坑五:consumer 错误依赖 provider JAR
**现象:** `shopping_manager_api` 启动时报错 `Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required`
**原因:** `shopping_manager_api` pom.xml 中错误地依赖了 `shopping_goods_service` 的 JAR 包,加上 `@ComponentScan("com.cyx")` 把 provider 的 MyBatis Mapper 也扫描进来了,但 consumer 没有数据源配置
**修复:** Dubbo 消费者只依赖 shopping_common(接口+POJO),不依赖具体实现模块
```xml
<!-- 错误 -->
<dependency>
<groupId>com.cyx</groupId>
<artifactId>shopping_goods_service</artifactId>
</dependency>
<!-- 正确:只引入公共接口 -->
<dependency>
<groupId>com.cyx</groupId>
<artifactId>shopping_common</artifactId>
</dependency>
```
---
## 坑六:自定义异常类构造器问题
**现象:** 异常被正确传到 consumer 端,`code` 有值但 `msg` 为 null
**原因:** 自定义异常类的问题分两层
**第一层:** 自己声明了 `private String message` 字段,和 Throwable 的 `getMessage()` 冲突。Lombok `@Data` 遇到父类已有 `getMessage()` 不会生成 getter,导致 `e.getMessage()` 返回 Throwable 的 `detailMessage`(从未赋值,始终 null)
**第二层:** hessian2 反序列化 Throwable 时需要标准的 `(String message)` 构造器来重建异常对象,没有这个构造器就设不上 detailMessage
**正确的自定义异常类写法:**
```java
@Data
public class busException extends RuntimeException {
private Integer code;
public busException() {}
// hessian2 反序列化必需的标准构造器
public busException(String message) {
super(message);
}
public busException(String message, Throwable cause) {
super(message, cause);
}
public busException(Integer code, String message) {
super(message); // 关键:传给父类构造器
this.code = code;
}
public busException(enumCode code) {
super(code.getMsg()); // 关键:传给父类构造器
this.code = code.getCode();
}
}
```
**要点:**
- **不要自己声明 `message` 字段**,复用 Throwable 的
- 所有构造器必须调 `super(message)`,确保 Throwable 的 `detailMessage` 被赋值
- **必须提供 `(String message)` 和 `(String message, Throwable cause)` 构造器**,hessian2 反序列化异常依赖这两个
---
## 总结
| 序号 | 问题 | 解决方案 |
|------|------|----------|
| 1 | Nacos artifactId 找不到 | 改用 `spring-cloud-starter-alibaba-nacos-*` |
| 2 | 父 POM 找不到 | `<relativePath>../pom.xml</relativePath>` |
| 3 | fastjson2 拦截异常 | 切 hessian2,`prefer-serialization: hessian2` |
| 4 | 异常处理器不生效 | `@ComponentScan("com.cyx")` 或 `AutoConfiguration.imports` |
| 5 | consumer 误依赖 provider | consumer 只依赖 shopping_common |
| 6 | 自定义异常 msg 为 null | 调 `super(message)` + 提供标准构造器 |
Dubbo 自定义异常 + fastjson2 序列化踩坑记录
_