在云栈社区的技术交流中,自定义异常的处理层次是一个常见议题。其核心不在于具体放置在哪一层,而在于理解异常的本质:它描述了业务流程中为何无法继续执行。自定义业务异常通常定义在公共模块或业务层,由Service层抛出,并由接口层统一处理。
一、异常的核心作用是什么?
异常并非日志,也不是返回值的替代品。它只做一件事:中断当前流程,并说明“为什么不能继续”。因此,判断异常应该定义在哪一层的关键在于:谁最清楚“不能继续”的原因。
二、为什么Controller层不适合定义业务异常?
很多开发者在初期可能会这样做:
throw new BizException("参数不合法");
将异常直接写在Controller里看似顺理成章,但实则存在问题:
- Controller仅负责参数接收和请求转发,并不掌握完整的业务规则。
- 同一业务规则可能被多个入口调用。
- 导致异常逻辑分散、重复判断,修改规则时需改动多个接口。
Controller层不应成为业务判断的中心。
三、Service层:异常最合理的归属
正确的做法是在Service层抛出异常,例如:
@Service
public class OrderService {
public void createOrder(Order order) {
if (stock < order.getCount()) {
throw new BizException("库存不足");
}
}
}
原因很明确:
- Service层掌握完整的业务语义。
- 它清楚哪些情况必须中断流程。
- 它知道失败时是否需要回滚事务。
只有在Service层抛出的异常,才是真正的“业务异常”。
Controller层则只需简单调用:
@PostMapping("/order/create")
public Result<?> create() {
orderService.createOrder(order);
return Result.success();
}
四、异常类本身的存放位置
1. 公共业务异常(通用型)
com.xxx.common.exception.BizException
适用于参数不合法、状态不允许、权限不足等通用场景,可被多个模块复用。
2. 模块级业务异常(复杂业务)
com.xxx.order.exception.OrderException
当某个业务领域异常众多、语义强烈时,可单独拆分为模块级异常。但前提是:异常必须使用“业务语言”描述,而非技术分类。
五、哪些地方不应定义业务异常?
❌ Mapper层
Mapper仅负责数据访问,不理解业务语义。它可以抛出系统异常(如SQL异常),但不该抛出业务异常。
❌ 工具类或Util
工具类只提供通用能力,不涉及业务规则。一旦在其中抛出业务异常,其复用性将大打折扣。
总结来说,异常属于业务逻辑范畴,不应定义在接口层或数据层,而应归属于掌握业务语义的Service层。
|