java service层应规范抛出自定义异常,语义明确、分层隔离、统一处理:定义具体名称的runtimeexception子类,含错误码枚举、标准构造器;service中仅抛出有意义业务异常;controller通过@controlleradvice统一响应,避免反模式。

在 Java 的 Service 层规范抛出自定义异常,核心是做到语义明确、分层隔离、统一处理。不要直接 throw new RuntimeException("xxx"),也不要在 Service 里 catch 后吞掉业务异常。关键是让异常能表达业务意图,并被上层(如 Controller)识别和响应。
定义清晰的自定义异常类
按业务场景分组设计异常,通常继承 RuntimeException(避免强制 try-catch),并提供标准构造方法:
- 带错误码(int 或 String)、消息、可选原因(Throwable)的构造器
- 错误码建议用枚举统一管理,例如
ErrorCode.USER_NOT_FOUND - 避免泛泛的
BusinessException,优先使用具体名称,如UserLockedException、InsufficientBalanceException
Service 方法中只抛出有意义的业务异常
Service 方法应聚焦业务逻辑判断,发现非法状态时立即抛出对应异常,不包装、不静默:
客服回复模板。售前咨询、售后处理、退换货、投诉回复、好评引导、升级处理、行业FAQ、满意度挽回。Customer service reply templates for pre-sale, after-sale, returns, complaints, escalation, FAQ generation, s...
- 校验失败:用户不存在、参数越界、状态不匹配 → 抛出对应异常
- 资源冲突:重复创建、并发修改 → 抛出
DuplicateResourceException等 - 外部依赖失败(如调用第三方 API 超时)→ 可封装为
ThirdPartyCallException,但需区分是否属于“业务可恢复”错误 - 不建议在 Service 中捕获并转换底层异常(如 DAO 的 SQLException),应由 DAO 层或统一持久层适配器完成转换
配合全局异常处理器统一响应
Controller 层不写 try-catch,而是依赖 @ControllerAdvice + @ExceptionHandler:
- 为每类自定义异常编写专门的 handler,返回标准化 JSON(含 code、message、timestamp)
- 对不同异常设置不同 HTTP 状态码(如 400、404、409、500)
- 记录日志时带上错误码和关键上下文(如 userId、orderId),方便排查
- 敏感信息(如数据库字段名、堆栈)不能直接返回给前端
避免常见反模式
以下做法会破坏异常规范性:
- 在 Service 中
try-catch自定义异常后又重新 throw 新异常,丢失原始语义 - 用字符串拼接构造异常消息,导致难以国际化或动态替换变量
- 把所有异常都转成同一个
AppException,靠 message 字符串区分 —— 失去类型安全和 handler 可识别性 - 在 Service 中抛出 checked 异常(如 Exception 子类),迫使调用方硬编码处理,违背 Spring 的运行时异常惯例
不复杂但容易忽略:异常不是错误日志的替代品,而是业务契约的一部分。Service 抛什么异常,就等于告诉上层“这里发生了哪种确定的业务失败”,后续流程该重试、回滚还是提示用户,都基于这个信号做决策。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










