catch(exception e)是埋雷起点,因吞掉runtimeexception致问题延迟暴露;应仅顶层宽泛捕获并记录堆栈,业务层只捕获可处理的明确异常类型。

Java 里 catch Exception 不是“兜底”,而是埋雷的起点。
为什么 catch (Exception e) 在生产代码里基本等于 bug
它会吞掉本该中断流程的 RuntimeException(比如 NullPointerException、ArrayIndexOutOfBoundsException),让问题延迟暴露,甚至掩盖资源泄漏或状态不一致。真正的异常处理目标不是“不让程序挂”,而是“让错误可定位、可恢复、可监控”。
- 只在顶层入口(如
main、ServletdoGet、Spring@ExceptionHandler)做宽泛捕获,且必须记录完整堆栈 + 上下文 - 业务逻辑层应只捕获明确知道如何处理的异常类型,例如
IOException重试、SQLException转换为业务异常 - 永远不要在
catch块里只写e.printStackTrace()或空catch—— 这等于关掉告警
try-with-resources 不是语法糖,是资源泄漏的开关
它强制要求资源实现 AutoCloseable,并在 try 块退出时调用 close(),哪怕发生异常。但很多人忽略:如果 close() 自身抛出异常,它会被压制(suppressed),不会打断原有异常流 —— 这意味着你可能根本不知道关闭失败了。
- 检查关键资源是否真的实现了
close()的幂等性和健壮性(比如某些 JDBC 驱动在连接已断时调用close()会抛新异常) - 需要感知关闭失败时,手动调用
close()并处理其异常,而不是依赖try-with-resources默默压制 -
InputStream、Connection、PreparedStatement等都适用;但Thread、ExecutorService不行 —— 它们不实现AutoCloseable
自定义异常该继承 RuntimeException 还是 Exception?
取决于调用方是否“必须”处理它。Java 强制检查型异常(checked exception)要求方法签名声明 throws,调用方必须 try/catch 或继续上抛。但这在现代 Java 项目中几乎成了反模式。
立即学习“Java免费学习笔记(深入)”;
- 95% 的业务异常应该继承
RuntimeException(如InsufficientBalanceException),避免污染接口契约和调用链 - 仅当异常代表外部系统不可控故障(如网络暂时不可达、文件系统只读)、且调用方有明确重试/降级策略时,才考虑 checked exception
- 所有自定义异常必须提供带
Throwable构造函数,否则丢失原始根因
Spring 中 @ControllerAdvice 捕获异常后,HTTP 状态码怎么设才不翻车
默认返回 500,但客户端(尤其是前端或下游服务)靠状态码做路由和重试。设错会导致熔断失效、告警误报、前端死等 loading。
- 对客户端输入错误(如参数校验失败),抛
IllegalArgumentException并用@ResponseStatus(HttpStatus.BAD_REQUEST)显式标注 - 对业务规则拒绝(如“余额不足”),抛自定义异常并配
@ResponseStatus(HttpStatus.CONFLICT)或409,比 400 更语义清晰 - 绝对不要在
@ExceptionHandler方法里手动写response.setStatus(401)—— 它绕过 Spring 的异常处理机制,可能跳过日志、监控和 CORS 头注入
最常被忽略的一点:异常消息里混入敏感数据(如数据库连接串、用户密码、内部路径),日志一打全泄露。所有异常构造时,确保 getMessage() 不含机密,必要时用 toString() 或额外字段承载调试信息。










