java异常包装的核心是保留原始cause、升维业务语义、控制传播范围;需在边界层(如dao)用语义明确的runtimeexception子类包装,禁止丢弃cause或暴露技术细节。

Java 异常转换与包装的核心是“保留原始原因、明确业务语义、控制异常传播范围”。面试官想看的不是你会不会 throw new RuntimeException(e),而是你能否在真实场景中做出合理设计。
理解包装的本质:为什么不能直接抛原始异常?
底层异常(如 SQLException、IOException)往往暴露技术细节,与上层业务无关;直接抛出会破坏抽象层次,导致调用方被迫处理不关心的实现细节。包装的关键在于语义升维——把“数据库连不上”变成“用户信息加载失败”。
- 原始异常要通过 cause 参数保留(即 new BizException("获取用户失败", e)),确保栈追踪可追溯
- 包装后的异常应继承 RuntimeException(非检查异常)或特定业务异常类,避免强制 try-catch 干扰业务流程
- 不要吞掉异常(e.printStackTrace() 或空 catch),也不要用 e.getMessage() 拼接新消息而丢弃 cause
常见包装方式及适用场景
面试常考三种典型写法,区别在于封装粒度和职责划分:
- 方法内即时包装:DAO 层捕获 SQLException,立即包装为 DataAccessException 并抛出 —— 适合异常含义明确、无需额外上下文的场景
- 统一异常处理器(@ControllerAdvice):Web 层全局捕获底层异常,转为标准 API 响应(如 {"code":500,"msg":"系统繁忙"})—— 适合 Spring Boot 项目,解耦业务逻辑与错误格式
- 链式包装增强信息:在原有异常基础上补充上下文,例如 new BizException("订单创建失败(用户ID:" + userId + ")", e) —— 便于排查时快速定位操作主体
面试高频陷阱题怎么答
比如被问:“Service 调用 Mapper 抛出 MyBatisException,你如何处理?” 正确思路不是“加个 try-catch 就行”,而是分层回应:
- Mapper 层保持原样(框架异常,不处理)
- DAO 或 ServiceImpl 中捕获,包装为自定义 DataAccessException,并传入原始异常作为 cause
- Service 接口声明抛出的是业务异常(如 UserNotFoundException),而非技术异常 —— 体现接口契约清晰
- 若该异常属于预期失败(如查不到用户),可考虑用 Optional 或返回 Result
避免异常流,这比包装更优雅
加分点:体现工程意识
高分回答会带出实际约束:
- 日志记录时打印 root cause(e.getCause() 可能有多层,要用 Apache Commons Lang 的 ExceptionUtils.getRootCause)
- 敏感信息过滤:包装时检查原始异常消息是否含密码、SQL 片段,做脱敏处理
- 监控告警:对特定包装后的异常(如 PaymentFailureException)打标,接入 SkyWalking 或 Prometheus 做失败率统计
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











