Dubbo 自定义异常 + fastjson2 序列化踩坑记录

_
# 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)` + 提供标准构造器 |

企业级商城秒杀(redis+java等后端技术) 2026-05-08
构造器注入的原理及优势 2026-06-09

评论区