java web开发中异常处理最佳方案

发布于:2025-07-06 ⋅ 阅读:(15) ⋅ 点赞:(0)

开发中异常处理会出现多次抛出的情况

  • 配合全局异常处理器在最外层捕获异常
  • 这里我们使用自定义异常BusinessException,DataAccessException,异常的名字可以快速看出异常发生在mvc的哪一层。
  • 每次重新抛出,都会在原先异常的基础上面加上Caused by 和at,也就是异常发生原因和异常发生位置。
  • 查看异常时,从下往上看,最下层也就是异常发生的最根本位置。
  • 每次抛出时都必须传递原始异常e,如果不传递,每次重新抛出时,原来异常发生的原因和位置就没了,只剩新抛出异常的原因和位置。最终导致堆栈断裂,无法定位到异常发生的最根本位置。
// 控制器层
public class UserController {
    public void getUserInfo() {
        userService.getUser();  // 最终抛出 BusinessException
    }
}
// 业务服务层
public class UserService {
    void getUser() {
        try {
            userDao.queryUser();
        } catch (DataAccessException e) {
            throw new BusinessException("用户查询失败", e);  // 再次包装
        }
    }
}

// 数据访问层
public class UserDao {
    void queryUser() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new DataAccessException("驱动加载失败", e);  // 包装原始异常
        }
    }
}
Exception in thread "main" BusinessException: 用户查询失败
    at UserService.getUser(UserService.java:10)  // 最外层抛出点
    at UserController.getUserInfo(UserController.java:5)
    
Caused by: DataAccessException: 驱动加载失败  // 中间层异常
    at UserDao.queryUser(UserDao.java:6)
    at UserService.getUser(UserService.java:8)
    
Caused by: ClassNotFoundException: com.mysql.jdbc.Driver  // 根本原因
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at UserDao.queryUser(UserDao.java:4)
    ... 2 more

不使用全局异常处理器

分层显式捕获并记录日志

// 控制层
@RestController
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable String id) {
        try {
            return userService.getUser(id);
        } catch (BusinessException e) {
            logger.error("请求 /user/{} 失败", id, e);  // 记录请求路径和参数
            return null;  // 或返回错误响应
        }
    }
}

// 业务服务层
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    public User getUser(String userId) {
        try {
            return userDao.queryUser();
        } catch (RepositoryException e) {
            logger.error("用户 {} 查询失败", userId, e);  // 记录业务上下文
            throw new BusinessException("查询失败", e);  // 包装并抛出
        }
    }
}

// 数据访问层
public class UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    public User queryUser() {
        try {
            // 模拟数据库操作
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            logger.error("数据库驱动加载失败", e);  // 记录原始异常
            throw new RepositoryException("驱动错误", e);  // 包装并抛出
        }
        // ... 其他数据库操作
    }
}

日志输出结果

  • 共输出3次日志,每次记录异常信息(如参数)和原始异常
  • 看日志时看每一次的输出信息,查看每一层完整的参数信息。
ERROR UserDao - 数据库驱动加载失败
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
    at UserDao.queryUser(UserDao.java:6)

ERROR UserService - 用户 123 查询失败
com.example.RepositoryException: 驱动错误
    at UserDao.queryUser(UserDao.java:8)
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
    at UserDao.queryUser(UserDao.java:6)

ERROR UserController - 请求 /user/123 失败
com.example.BusinessException: 查询失败
    at UserService.getUser(UserService.java:10)
Caused by: com.example.RepositoryException: 驱动错误
    at UserDao.queryUser(UserDao.java:8)