该如何正确处理异常信息
异常处理是 Java 开发中非常重要的一个知识点。
很多新人在处理异常的时候,要么不知道什么时候该 try-catch、什么时候该抛出异常,要么 try-catch 里面什么都不做,直接把异常”吞掉”。
这两种做法都是不对的。
什么时候抛异常、什么时候 try-catch 处理异常,然后 try-catch 处理异常该怎么处理?
一、什么时候 try-catch
当你不想让调用方察觉到异常时,就用 try-catch 悄悄接住:只把日志写足,别把异常抛出去。
同样,当你不想让一次异常中断整个流程、导致后续代码无法运行时,也用 try-catch 把它拦下来。
更进一步,把 try-catch 当成异常场景下的”降级开关”:一旦出事,立刻捕获,记录日志、返回默认值或缓存数据,让用户依旧能看到页面,让业务流程继续走下去。
1、隐藏异常,不让调用者感知异常,并做降级处理
典型的场景是:一个页面有核心数据和推荐数据两部分,推荐数据来自一个不太稳定的外部服务。如果推荐服务挂了,不应该让整个页面都报错,而是返回空列表,确保核心功能正常。
@Service@Slf4jpublic class ProductService { @Autowired private RecommendService recommendService; // 可能不稳定的推荐服务
public ProductDetail getProductDetail(Long productId) { ProductDetail detail = productDao.getById(productId); try { // 获取商品推荐列表(非核心功能) List<Product> recommendations = recommendService.getRecommendations(productId); detail.setRecommendations(recommendations); } catch (Exception e) { // 核心:捕获所有异常,确保详情页主功能不受影响 log.warn("获取商品[{}]推荐列表失败,将返回空推荐列表", productId, e); detail.setRecommendations(Collections.emptyList()); // 降级:返回空列表 }
return detail; // 主商品信息一定正常返回 }}这种写法的关键是:日志要写足,降级逻辑要合理,不要什么都不做。
2、对原始异常进行包装
当需要对原始异常信息进行包装的时候,也需要使用 try-catch。
这个时候在 try-catch 里面抛出的异常,应该是自定义异常。因为自定义异常返回的信息是清晰明了的,能告诉用户目前出现了什么情况,而不是直接把原始异常信息暴露给用户。
直接把原始异常信息暴露出去,有两个问题:
- 用户体验差:用户看到一堆技术栈信息,完全不知道发生了什么
- 安全隐患:原始异常可能包含数据库 IP、表名、SQL 语句等敏感信息
错误的示例:
public User login(String username, String password) throws SQLException { // 直接调用 DAO,可能抛出 SQLException return userDao.findByUsernameAndPassword(username, password);}如果数据库连接失败,前端可能收到包含数据库 IP、表名、SQL 语句片段的错误信息,体验极差且不安全。
正确的做法:
// 1. 首先,定义一个业务异常public class AuthenticationException extends RuntimeException { public AuthenticationException(String message, Throwable cause) { super(message, cause); }}
// 2. 在 Service 层进行包装public User login(String username, String password) { try { User user = userDao.findByUsernameAndPassword(username, password); return user; } catch (Exception e) { // Spring 对 SQLException 等的包装 // 1. 关键:记录原始异常,供后续排查 log.error("用户[{}]登录时数据访问异常", username, e); // 2. 关键:抛出一个对上游和用户友好的业务异常 throw new AuthenticationException("系统繁忙,请稍后再试!"); }}这种写法的关键是:原始异常记到日志里,给用户和上游抛出友好的业务异常。
二、什么时候抛出异常
在程序执行过程中,遇到核心业务前提条件不满足或关键流程出现不可恢复的错误,导致后续所有操作变得无意义或不可能成功时,此时就应该抛出异常并终止当前执行流。
简单来说:条件不满足就别往下走了,直接抛异常。
public void payOrder(Order order, Payment payment) {
// 条件1:订单状态检查 if (!"PENDING".equals(order.getStatus())) { // 后续支付逻辑已无意义,立即抛出! throw new IllegalOrderStateException( "订单[id=" + order.getId() + "]当前状态为" + order.getStatus() + ",无法支付。" ); }
// 条件2:支付金额校验 if (payment.getAmount().compareTo(order.getTotalAmount()) != 0) { // 金额不匹配,支付必然失败,立即抛出! throw new PaymentAmountMismatchException( "支付金额[" + payment.getAmount() + "]与订单总额[" + order.getTotalAmount() + "]不符。" ); }
// 条件3:执行核心支付(只有所有前提满足,才执行到这里) try { paymentGateway.execute(payment); } catch (BankGatewayException e) { // 支付本身失败,同样抛出业务异常 throw new PaymentFailedException("银行支付处理失败,请重试"); }
// 更新状态(只有一切成功,才执行到这里) order.setStatus("PAID"); orderRepository.update(order);}抛异常的核心原则:后续代码已经没有意义执行了,就立刻抛出,不要继续往下走。
三、总结
| 场景 | 处理方式 | 关键点 |
|---|---|---|
| 非核心功能异常 | try-catch + 降级 | 日志写足,返回默认值 |
| 原始异常不友好 | try-catch + 自定义异常 | 记录原始异常,抛出友好信息 |
| 核心前提不满足 | 直接抛出异常 | 后续无意义,立即终止 |
| 不可恢复的错误 | 直接抛出异常 | 配合业务异常类使用 |
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!














