[Java实战经验]异常处理最佳实践

一些好的异常处理实践。

目录

异常设计

自定义异常

自定义异常设计,如业务异常定义BusinessException,设置一个基础异常类,如XXAppBaseException(或就叫BaseException),然后让各类异常继承,如下:

java 复制代码
public class UserException extends XXAppBaseException { ... }

public class MapException extends XXAppBaseException { ... }

这里异常的划分可以按照模块、业务来区分,也可以分离业务代码异常与技术代码异常。

为异常设计错误代码(状态码)

常见的异常代码设计有HTTP的异常状态码,如404、500、502这种。

这样做主要是便于日志分析和客户端处理,很明显,使用错误代码做筛选能提升检索效率、方便收集、自动化处理,且使用异常状态码来传输异常信息提升了信息传输与存储效率。

等等......

设计粒度

自定义异常和异常错误代码都是比较常见的操作,但是设计时需要考虑粒度。

一般有层级关系的设计更便于理解、维护。

在自定义异常中就是多层继承关系,在异常错误码中就是分层错误码设计,如全局错误码 > 模块错误码 > 具体错误码

复制代码
5xx--->5xxx->5xxxx

全局异常处理

使用Spring的@ControllerAdvice或类似机制统一处理异常。如:

java 复制代码
/**
 * 全局异常处理器
 * 
 * @author ruoyi
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 权限校验异常(ajax请求返回json,redirect请求跳转页面)
     */
    @ExceptionHandler(AuthorizationException.class)
    public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
        if (ServletUtils.isAjaxRequest(request))
        {
            return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
        }
        else
        {
            return new ModelAndView("error/unauth");
        }
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
            HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e, HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public Object handleServiceException(ServiceException e, HttpServletRequest request)
    {
        log.error(e.getMessage(), e);
        if (ServletUtils.isAjaxRequest(request))
        {
            return AjaxResult.error(e.getMessage());
        }
        else
        {
            return new ModelAndView("error/service", "errorMessage", e.getMessage());
        }
    }
}

异常日志信息保留

在抛出异常时,建议连带当前业务标识信息一起抛出(每一层日志抛出记录标识),这样方便排查问题。

异常处理时机

偶尔能看到一类将所有代码块都包起来的try-catch异常处理,这种代码被称为"防御式编程"的过度使用,会导致多种问题。

  1. 代码可读性降低:大量的异常处理代码掩盖了业务逻辑
  2. 异常信息丢失:每层都捕获并重新抛出,可能丢失原始堆栈信息
  3. 难以维护:当需要修改异常处理逻辑时,需要修改多处代码
  4. 性能影响:过多的try-catch会对性能产生轻微影响

异常处理应该在能够正确响应的层级进行,如:

  1. 边界层处理:如API层、用户界面层、外部系统集成点
  2. 业务决策点处理:在能够做出恢复决策的地方处理异常
  3. 资源管理点处理:在使用需要清理的资源的地方处理
java 复制代码
// 不好的做法
public void badExceptionHandling() {
    try {
        // 获取用户
        User user = null;
        try {
            user = userRepository.findById(userId);
        } catch (Exception e) {
            log.error("Failed to get user", e);
        }
        
        // 获取订单
        Order order = null;
        try {
            order = orderRepository.findById(orderId);
        } catch (Exception e) {
            log.error("Failed to get order", e);
        }
        
        // 处理业务逻辑
        try {
            processOrder(user, order);
        } catch (Exception e) {
            log.error("Failed to process order", e);
        }
    } catch (Exception e) {
        log.error("Unexpected error", e);
    }
}
java 复制代码
// 好的做法
public void goodExceptionHandling() {
    try {
        User user = userRepository.findById(userId);
        Order order = orderRepository.findById(orderId);
        processOrder(user, order);
    } catch (UserNotFoundException e) {
        // 有针对性地处理用户不存在的情况
        log.warn("Order processing failed: User not found", e);
        notifyAdministrator(e);
    } catch (OrderNotFoundException e) {
        // 有针对性地处理订单不存在的情况
        log.warn("Order processing failed: Order not found", e);
        notifyCustomer(userId, e);
    } catch (BusinessException e) {
        // 处理所有业务异常
        log.warn("Business rule violation during order processing", e);
        // 可能的补救措施
    } catch (Exception e) {
        // 处理所有其他未预期的异常
        log.error("Unexpected error during order processing", e);
        // 紧急措施
    }
}

资源管理

try-with-resources

使用try-with-resources自动关闭资源,防止泄露。为自己的资源类实现AutoCloseable接口,如:

java 复制代码
public class ResourceLock implements AutoCloseable {
    // 获取资源
    public ResourceLock() { /* 获取锁或资源 */ }
    
    @Override
    public void close() { /* 释放锁或资源 */ }
}

使用finally和这个一样。常规操作。

异常中的事务

在Spring框架中,默认情况运行时异常与严重问题会导致事务回滚,检查型异常不会。

  • 运行时异常(unchecked) :继承自RuntimeException的异常,默认导致事务回滚
  • 检查型异常(checked) :继承自Exception但不是RuntimeException的子类,默认不会导致事务回滚
  • Error :严重问题,如OutOfMemoryError,默认导致事务回滚

因此,我们需要注意异常对事务的影响。

相关推荐
柒七爱吃麻辣烫5 分钟前
在Linux中安装JDK并且搭建Java环境
java·linux·开发语言
极小狐14 分钟前
极狐GitLab 容器镜像仓库功能介绍
java·前端·数据库·npm·gitlab
努力的搬砖人.38 分钟前
如何让rabbitmq保存服务断开重连?保证高可用?
java·分布式·rabbitmq
_星辰大海乀1 小时前
数据库约束
java·数据结构·数据库·sql·链表
多多*1 小时前
Java反射 八股版
java·开发语言·hive·python·sql·log4j·mybatis
码农飞哥1 小时前
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
java·数据库·spring boot·缓存·微服务·消息队列·面试技巧
liudongyang1231 小时前
jenkins 启动报错
java·运维·jenkins
曹牧1 小时前
JSON 实体属性映射的最佳实践
java
qq_543248522 小时前
Tomcat服务部署
java·tomcat
Auc242 小时前
OJ判题系统第4期之判题机模块架构——设计思路、实现步骤、代码实现(工厂模式、代理模式的实践)
java·spring cloud·log4j·mybatis·代理模式·工厂模式