非受检异常更适合微服务内部流转,因其避免层层throws声明、减少重复包装、符合开闭原则;通过统一基类+语义化子类、边界处受检→非受检转换、断言简化校验,实现干净接口与清晰调用链。
用非受检异常(runtimeexception 及其子类)替代受检异常,能显著减少微服务中层层 throws 声明的冗余代码,让接口签名干净、调用链清晰。
为什么非受检异常更适合微服务内部流转
微服务间调用通常已有统一网关或全局异常处理器,业务异常不需要在每一层都显式声明和捕获。受检异常强制要求每个中间方法添加 throws,导致:
- Service → Manager → DAO 层全得写
throws IOException/SQLException,但实际只在 Controller 或网关层做统一响应封装 - 同一异常在多层重复包装(如
new ServiceException(new DaoException(...))),堆栈变深、语义模糊 - 新增一个校验分支就得改一串方法签名,违反开闭原则
关键设计:统一基类 + 语义化子类
不直接 throw RuntimeException,而是定义轻量、可扩展的异常体系:
- 定义一个抽象基类(如
BaseException),含errorCode、httpStatus、logLevel等字段 - 按业务域建少量子类:如
UserException、OrderException,每个类内用静态工厂方法封装常见错误码 - 所有子类继承
RuntimeException,无需throws声明
示例:UserException.userNotFound("U123") 直接抛出,从 DAO 到 Service 到 Controller 全程无声明、无 catch,最终由全局 @ControllerAdvice 捕获并转成标准 JSON 响应。
边界处做一次“受检→非受检”转换
第三方 SDK 或底层 API(如文件读取、HTTP 客户端)返回受检异常时,不要让它穿透整个调用链:
- 在最靠近外部依赖的地方(如 Adapter 层或 Feign Client 回调)立即转换:把
IOException包装为RemoteCallException - 转换时保留原始异常作为 cause,便于排查;同时赋予业务含义(如 “支付网关连接超时” 而非 “SocketTimeoutException”)
- 避免在 Service 方法里写
try-catch-throw new XxxException(e),应集中到统一转换器或模板方法中
配合断言简化校验逻辑
参数校验、状态检查等高频场景,可用自定义断言替代 if-throw 模板代码:
- 写一个
Asserts.checkNotNull(user, UserException::userNotFound) - 或更进一步:用 Lombok 的
@NonNull+ 自定义注解处理器,在编译期/运行期自动注入校验逻辑 - 这样主流程只剩核心业务,异常抛出变成一行声明式调用,不打断阅读节奏











