统一拦截异常 @RestControllerAdvice

相关内容来自腾讯元宝, 作者也属于入门,仅做参考

我们学习了异常, 除了具体业务里针对不同的异常, 我们还可以统一拦截异常

创建自定义异常

前面我们学习了异常类的知识, 有个知识点叫抛出自定义异常, 如何定义呢, 我们定义一个粗略的业务异常

public class BusinessException extends Exception {
	public BusinessException(String message) {
		super(message);
	}
}

这个自定义的异常就是要继承Exception或其他Exception的子类, 我们这个类就继承了异常类的特性.
比如订单,金额,是否为空等, 写到不满足业务条件 我们就可以throw

throw new BusinessException("这是一个自定义的异常")

Throwable
├── Exception
│ ├── RuntimeException
│ │ ├── DataAccessException (Spring)
│ │ │ ├── DuplicateKeyException
│ │ │ ├── DataIntegrityViolationException
│ │ │ └── …
│ │ ├── IllegalArgumentException
│ │ └── …
│ ├── IOException
│ └── …
└── Error

下面是继承的Exception

自定义一个异常类来 处理运行时的异常

package com.example.demo.autil;

import lombok.Data;

/*
@Data相当于以下注解的组合:
@Getter:生成所有字段的 getter 方法
@Setter:生成所有字段的 setter 方法(final 字段除外)
@ToString:生成 toString() 方法
@EqualsAndHashCode:生成 equals() 和 hashCode() 方法
@RequiredArgsConstructor:生成包含 final 字段和 @NonNull 字段的构造方法
 */
@Data
public class AppBusinessException extends Exception{
    private int code;
    private String message;
    public AppBusinessException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public AppBusinessException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }
}

下面是继承的RuntimeException

package com.example.demo.autil;

/**
 * 自定义异常类 需要继承 RuntimeException 然后需要构造函数
 */
public class AppException extends RuntimeException {
    private int code;
    private String message;
    public AppException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public AppException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

下面是个通用的统一拦截异常

@RestControllerAdvice 注解

package com.example.demo.autil;

import com.example.demo.aenum.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.example.demo.autil.ResultUtil;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 全局捕获异常, 这个类可以监听服务器,数据库操作,自定义异常等
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

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

    /**
     * 处理所有传参异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseEntity<?> handleException(MissingServletRequestParameterException e) {

        String message = String.format("缺少必需参数: %s (类型: %s)",
                e.getParameterName(), e.getParameterType());

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(message));
    }

    /**
     * 处理所有业务的异常
     */
    @ExceptionHandler(value = AppBusinessException.class)
    public ResponseEntity<?> handleException(AppBusinessException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(e.getMessage()));
    }

    /**
     * 处理所有SQL异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleException(Exception e) {
        logger.error("系统异常---: ", e);

        // 自定义异常--
        if (e instanceof AppException) {
            AppException appException = (AppException) e;
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed(appException.getCode(), appException.getMessage()));
        }
        // 根据异常类型返回不同的错误信息
        if (e instanceof DataAccessException) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed("数据库操作失败,请稍后重试"));
        }

        if (e instanceof RuntimeException) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed("系统运行异常"));
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed("系统内部错误"));
    }

    /**
     * 专门处理数据访问异常
     */
    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<?> handleDataAccessException(DataAccessException e) {
        logger.error("数据库访问异常: ", e);

        // 获取根异常信息
        Throwable rootCause = getRootCause(e);
        String errorMessage = "数据库操作失败";

        if (rootCause != null) {
            // 根据具体的SQL异常类型返回更详细的错误信息
            // 这个提示是根据唯一索引提示的
            if (rootCause.getMessage().contains("Duplicate entry")) {
                errorMessage = "数据已存在,请勿重复添加";
            } else if (rootCause.getMessage().contains("Data too long")) {
                errorMessage = "数据长度超过限制";
            } else if (rootCause.getMessage().contains("cannot be null")) {
                errorMessage = "必填字段不能为空";
            } else if (rootCause.getMessage().contains("foreign key constraint")) {
                errorMessage = "存在关联数据,无法删除";
            }
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(errorMessage));
    }

    private Throwable getRootCause(Throwable throwable) {
        Throwable cause = throwable;
        while (cause.getCause() != null) {
            cause = cause.getCause();
        }
        return cause;
    }
}

注意到: @ExceptionHandler(Exception.class) 和 @ExceptionHandler(DataAccessException.class) 冲突么?

不会冲突!​ Spring 会按照最精确匹配的原则执行异常处理。
@ExceptionHandler 注解的执行顺序和冲突处理
核心原则:精确匹配优先

下面例子写一个几种捕获异常的情况

   @GetMapping("getSystemError")
    public Result<?> getSystemError(@RequestParam String type, @RequestParam Integer id) throws AppBusinessException{
        if (type.equals("appBusiness")) {
            throw new AppBusinessException("业务异常");
        }
        // 如果我们接口就没有 ?type= 会怎么样呢
        return ResultUtil.success("成功");
    }
  1. 没有传参 ,统一拦截MissingServletRequestParameterException
    在这里插入图片描述

2.捕获业务的异常
在这里插入图片描述

  1. 正常情况
    在这里插入图片描述
### @RestControllerAdvice异常处理机制 @RestControllerAdvice 是 Spring 提供的一个组合注解,它实际上是@ControllerAdvice 和@ResponseBody 注解的合集。这个注解用于定义全局异常处理器类,在整个应用程序范围内捕获并处理控制器中的未处理异常。 #### 异常处理流程 当发生异常时,Spring MVC 会调用实现了 HandlerExceptionResolver 接口的对象来解析异常。如果使用了 @RestControllerAdvice,则会在其中定义的方法内执行具体的异常处理逻辑[^1]。 ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = RuntimeException.class) public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e){ ErrorResponse errorResponse = new ErrorResponse("500",e.getMessage()); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } } ``` 上述代码展示了如何通过 @RestControllerAdvice 创建一个名为 `GlobalExceptionHandler` 的全局异常处理器,并针对特定类型的异常提供统一响应格式。 #### 关联的拦截器 除了作为异常处理器外,@RestControllerAdvice 还能与其他组件协同工作以增强功能: - **AOP (面向切面编程)**: 可以利用 AOP 技术围绕方法调用来添加额外的行为,比如日志记录、性能监控等。 - **Interceptor**: 使用 WebMvcConfigurer 或者直接配置 Interceptor 来实现请求级别的预处理和后置处理操作。这些拦截器可以在进入目标 handler 方法之前或之后执行某些任务,例如身份验证检查、参数校验等。 对于与 @RestControllerAdvice 相关的具体拦截器来说: - 如果希望在所有 RESTful API 请求前进行某种通用的操作(如权限认证),可以通过注册自定义的 OncePerRequestFilter 实现此类需求; - 若要对返回的数据做进一步加工(如数据脱敏),则可在 ResponseBodyAdvice 中完成相应的工作; 综上所述,虽然 @RestControllerAdvice 主要关注于集中化管理和简化异常处理过程,但它也可以很容易地集成其他中间件服务从而形成更强大的解决方案体系[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值