真正实现业务逻辑与异常处理解耦靠分层设计、统一拦截和语义化封装;应使用@controlleradvice统一处理自定义业务异常,按领域建专用异常类,资源清理与补偿操作外置,重试与降级逻辑声明式配置。

多 catch 块本身不是解耦手段,而是异常分类的语法工具;真正实现业务逻辑与异常处理解耦,靠的是分层设计、统一拦截和语义化封装,而不是在 service 或 controller 里堆砌 try-catch。
用 @ControllerAdvice 统一接管异常
Spring 应用中,90% 的业务异常不该出现在业务方法内部。把所有 controller 层抛出的异常集中拦截,既避免重复代码,又保证日志、响应、监控的一致性。
- 定义自定义业务异常基类(如 BusinessException),所有校验失败、状态不满足等都继承它
- 在 @ControllerAdvice 类中写 @ExceptionHandler(BusinessException.class) 方法,提取用户 ID、traceId、请求路径等上下文
- 返回标准化错误响应体(如 { "code": 400, "msg": "库存不足", "traceId": "xxx" }),而非原始异常堆栈
- 避免在 handler 里调用 e.printStackTrace()——用 logger.error("业务失败: {}", action, e) 让框架自动附加堆栈
按领域语义定义异常,不依赖泛型 Exception
catch (Exception e) 是解耦的反面:它抹平了异常含义,迫使上层用 if-else 判断 e.getMessage() 来分支,反而加重耦合。
- 为关键业务环节建专用异常,例如:InsufficientStockException、PromotionExpiredException、PaymentTimeoutException
- 这些异常构造时就携带业务字段(订单号、商品 SKU、活动 ID),不依赖事后解析 message 字符串
- 下游调用方可通过 instanceof 或类型匹配直接决策:重试、降级、提示用户或记录告警
- 日志聚合平台可按异常类名统计各业务失败率,无需正则提取关键词
资源清理与补偿操作不混入业务主流程
文件读取失败后关闭流、支付超时后回滚预占库存——这类动作本质是“异常后的契约保障”,不是业务逻辑本身。
- 优先用 try-with-resources 自动释放流、连接、锁等资源,比手动 finally 更安全可靠
- 需要主动补偿时(如事务回滚、消息撤回),放到独立方法里,并确保该方法自身不抛新异常(可用 try-catch 包裹并记录 warn 级日志)
- 不要在 catch 块里写业务恢复代码(比如“库存扣减失败,改走积分抵扣”)——这属于策略分支,应由上层根据异常类型调度,而非硬编码在异常处理块中
重试与降级逻辑外置,不污染核心方法
网络调用失败要不要重试?重几次?间隔多久?这些是横切关注点,和“下单”这个动作无关。
- 用 spring-retry 或 guava-retrying 将重试策略声明式配置,例如:
.retryIfExceptionOfType(IOException.class).withWaitStrategy(fixedDelay(500)) - 把具体业务逻辑包装成 Callable 或 Supplier,交给重试器执行,原方法只专注“做什么”,不关心“失败了怎么办”
- 对必须降级的场景(如优惠券服务不可用),定义 fallback 方法,通过注解(如 @Retryable + @Recover)或函数式接口分离主备路径
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











