基于@ControllerAdvice和AOP的分层异常处理架构

在Spring应用中处理异常时,@ControllerAdvice和基于AOP的切面编程是两种核心思路。下面是一个结合了 @ControllerAdvice和 AOP 的完整分层异常处理架构示例。这个架构充分发挥了二者各自的优势,AOP 用于系统内部的异常监控和日志记录,@ControllerAdvice则专注于为前端提供统一、友好的 Web 异常响应。

整体架构设计思路

为了让你快速把握全局,我们先通过下面这张序列图来了解一个异常从发生到被处理的完整流程,以及 @ControllerAdvice和 AOP 在其中扮演的角色:

这个架构的核心是职责分离:

  • AOP 切面 :作为"监控者",在后台默默记录所有详细异常,用于开发和运维排查问题。
  • @ControllerAdvice :作为"协调者",在前台将任何异常转换成前端能理解的、统一的 JSON 响应格式。

接下来,我们看看具体的代码实现。

一、核心组件实现

1. 统一的响应体与异常类定义

首先,定义项目中的标准返回格式和自定义异常,为统一处理打下基础。

typescript 复制代码
// 1. 统一API响应封装
@Data
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    // 成功静态方法
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("成功");
        result.setData(data);
        return result;
    }

    // 失败静态方法
    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
}

// 2. 自定义业务异常(继承自RuntimeException)
public class BizException extends RuntimeException {
    private Integer code;
    private String message;

    public BizException(Integer code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }
    // 省略getter方法
}

// 3. 异常代码枚举
public enum ErrorCodeEnum {
    PARAM_ERROR(1001, "参数错误"),
    USER_NOT_FOUND(1002, "用户不存在"),
    SYSTEM_ERROR(5000, "系统繁忙,请稍后再试");

    private final Integer code;
    private final String message;
    // 构造方法和getter
}

2. AOP 切面:系统异常监控与日志记录(Service层)

在Service层使用AOP,目的是捕获并记录所有业务方法抛出的异常细节,这是给开发运维人员看的。

less 复制代码
@Aspect
@Component
@Slf4j
public class ServiceExceptionMonitorAspect {

    /**
     * 拦截所有Service层方法执行
     */
    @AfterThrowing(
        pointcut = "execution(* com.yourcompany.yourproject.service..*.*(..))", 
        throwing = "e"
    )
    public void logServiceException(JoinPoint joinPoint, Throwable e) {
        // 1. 记录详细的错误日志
        String methodName = joinPoint.getSignature().toShortString();
        Object[] args = joinPoint.getArgs();
        log.error("业务服务异常 - 方法: {} | 参数: {} | 异常: ", methodName, Arrays.toString(args), e);

        // 2. (可选)发送异常告警到监控系统,如Sentinel、Prometheus
        // alarmSender.sendAlert("业务异常", methodName, e.getMessage());

        // 注意:这里并不捕获或处理异常,只是记录和监控,异常会继续向上抛出
    }
}

3. @ControllerAdvice:全局Web异常处理器(Controller层)

这里是处理所有抵达Controller层的异常,并组装成返回给客户端的友好信息。

kotlin 复制代码
@RestControllerAdvice // 等同于 @ControllerAdvice + @ResponseBody
public class GlobalWebExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalWebExceptionHandler.class);

    /**
     * 处理自定义业务异常 - 返回400状态码(客户端请求问题)
     */
    @ExceptionHandler(BizException.class)
    public Result<?> handleBizException(BizException e, HttpServletRequest request) {
        // 记录WARN级别日志即可,因为业务异常通常是可预见的
        logger.warn("业务异常 - 请求路径: {} | 错误信息: {}", request.getRequestURI(), e.getMessage());
        // 直接返回业务异常信息给前端
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常(如@Validated触发的异常)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 提取校验失败中的第一条错误信息
        String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        return Result.error(ErrorCodeEnum.PARAM_ERROR.getCode(), errorMessage);
    }

    /**
     * 处理所有未精确定义的异常 - 返回500状态码(服务器内部错误)
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleAllOtherException(Exception e, HttpServletRequest request) {
        // 记录ERROR级别日志,因为这是未预期的系统错误
        logger.error("系统异常 - 请求路径: {} ", request.getRequestURI(), e);
        // 对前端返回模糊的友好信息,避免泄露系统内部细节
        return Result.error(ErrorCodeEnum.SYSTEM_ERROR.getCode(), ErrorCodeEnum.SYSTEM_ERROR.getMessage());
    }
}

二、在业务中的使用方式

定义好上述组件后,在Controller和Service中的使用会变得非常简洁。

kotlin 复制代码
// Service层示例
@Service
public class UserServiceImpl implements UserService {

    @Override
    public User findUserById(Long id) {
        // 模拟业务校验不通过,直接抛出业务异常
        if (id == null || id <= 0) {
            throw new BizException(ErrorCodeEnum.PARAM_ERROR.getCode(), "用户ID不合法");
        }

        // 模拟查询用户不存在
        User user = userRepository.findById(id);
        if (user == null) {
            throw new BizException(ErrorCodeEnum.USER_NOT_FOUND.getCode(), ErrorCodeEnum.USER_NOT_FOUND.getMessage());
        }

        // ... 其他可能抛出数据库异常等系统异常的逻辑
        return user;
    }
}

// Controller层示例(干净整洁,无需处理异常)
@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        // 直接调用Service,所有异常都会交由全局处理器处理
        User user = userService.findUserById(id);
        return Result.success(user);
    }
}

三、架构优势总结

这个结合了 @ControllerAdvice和 AOP 的混合架构带来了以下显著好处:

  1. 关注点分离,职责清晰

    • AOP切面 专注于系统内部的异常监控、日志记录和告警,服务于开发运维。
    • **@ControllerAdvice**专注于对外的 Web 响应格式统一,服务于前端客户端。
  2. 代码极大简化:Controller 和 Service 的方法主体逻辑非常纯粹,只需关注业务,所有的异常处理模板代码都被消除。

  3. 维护性和扩展性高:任何异常处理策略的变更(如日志格式、错误码映射)只需在一个地方修改。新增一种异常类型,也只需在全局处理器中添加一个方法。

  4. 安全与体验兼得 :通过 AOP 记录了完整的异常堆栈供内部排查,同时通过 @ControllerAdvice给前端返回了友好、不泄露敏感信息的错误提示。

这种分层异常处理架构是实践中非常成熟和推荐的模式,能够显著提升复杂项目的健壮性和可维护性。

相关推荐
Badman2 小时前
Cursor入门提效指南
后端·cursor
武子康2 小时前
大数据-145 Apache Kudu 架构与实战:RowSet、分区与 Raft 全面解析
大数据·后端·nosql
间彧2 小时前
Spring @ControllerAdvice详解与应用实战
后端
间彧3 小时前
@ControllerAdvice与AOP切面编程在处理异常时有什么区别和各自的优势?
后端
间彧3 小时前
什么是Region多副本容灾
后端
爱敲代码的北3 小时前
WPF容器控件布局与应用学习笔记
后端
爱敲代码的北3 小时前
XAML语法与静态资源应用
后端
清空mega3 小时前
从零开始搭建 flask 博客实验(5)
后端·python·flask
爱敲代码的北3 小时前
UniformGrid 均匀网格布局学习笔记
后端