SpringBoot优雅地处理全局异常,返回前端

笔者这边提供了两种处理全局异常的方式。这两种方式各有千秋,都很优雅。至于伙伴们想用哪种方式,那就仁者见仁,智者见智了。

0、公共部分

在介绍异常处理方式前,先定义一些公共的类。这些类在两种处理方式中都会用到。

【自定义业务异常】

/**
 * 自定义业务异常
 */
@Data
public class SunException extends RuntimeException {

    private Integer code;
    private String msg;

    public SunException(SystemEnum systemEnum) {
        this.code = systemEnum.getCode();
        this.msg = systemEnum.getDesc();
    }
    public SunException(BusinessEnum businessEnum) {
        this.code = businessEnum.getCode();
        this.msg = businessEnum.getDesc();
    }

    public SunException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

【自定义系统枚举】

/**
 * 系统枚举
 */
public enum SystemEnum {
    SUCCESS(0, "success"),
    FAIL(-1, "fail"),
    PARAM_ILLEGAL(100, "参数非法!"),
    SERVICE_TIME_OUT(200, "服务间调用超时"),
    UNEXPECTED_EXCEPTION(500, "系统内部错误,请联系管理员!"),
    OTHER(9999, "Unknown Exception.");

    private Integer code;
    private String desc;

    SystemEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

【自定义业务枚举】

/**
 * 业务类枚举
 *
 * @author: dong
 * @date: 2023/2/12 21:50
 * @since: 1.0
 */
public enum BusinessEnum {
    /**--------用户相关----------**/
    USER_ID_NOT_EXIST(1000, "userId not exist."),

    /**--------任务相关----------**/
    TASK_ID_NOT_EXIST(2000, "taskId not exist."),
    TASK_NAME_NOT_EXIST(2001, "taskName not exist."),
    TASK_TYPE_NOT_EXIST(2002, "taskType not exist."),
    DEADLINE_NOT_EXIST(2003, "deadline not exist."),
    CONTENT_NOT_EXIST(2004, "content not exist.")
    ;

    private Integer code;
    private String desc;

    BusinessEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

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

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

【统一封装响应体】

/**
 * 统一封装响应体
 * @param 
 */
public class BaseResult {
    private Integer code;
    private String msg;
    private T data;

    public BaseResult() {
    }

    public BaseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public BaseResult(T data) {
        this.code = SystemEnum.SUCCESS.getCode();
        this.msg = SystemEnum.SUCCESS.getDesc();
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

【响应结果工具类】

/**
 * 响应结果工具类
 */
public class ResultUtil {

    public static  BaseResult outSuccess() {
        return new BaseResult<>(SystemEnum.SUCCESS.getCode(), SystemEnum.SUCCESS.getDesc(), null);
    }
    public static  BaseResult outSuccess(R data) {
        return new BaseResult<>(data);
    }

    public static  BaseResult outFail(String errorMsg) {
        return new BaseResult<>(SystemEnum.FAIL.getCode(), errorMsg, null);
    }
    public static  BaseResult outFail(Integer errorCode, String errorMsg) {
        return new BaseResult<>(errorCode, errorMsg, null);
    }
}

方式一、@RestControllerAdvice + @ExceptionHandler

如下所示,

a. 新建一个全局异常处理类,并在类名前加上@RestControllerAdvice注解,该注解可以拦截项目中抛出的异常;

b. 同时在新建一个处理异常的方法,并在方法上加上@ExceptionHandler注解,并在该注解的属性中指定具体的异常。如下代码中指定的具体异常即是 SunException(也就是上一小节中笔者自定义的业务异常)。

c. 在处理异常的方法中,通过ResultUtil.outFail() 方法统一封装返回给前端的响应体。

/**
 * 全局异常处理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

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

    @ExceptionHandler(SunException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public BaseResult handlerBusinessException(SunException sunException) {
        LOGGER.error("exception happened at {}", sunException.getMsg());
        return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
    }
}

那么在具体的业务接口中如何抛出异常能被GlobalExceptionHandler所捕获呢?

其实很简单,只要用 throw new SunException(...); 就可以了。注意:这里只能抛出SunException,不能抛出RuntimeException或者任何其他异常。因为@ExceptionHandler已经指定了具体的异常类型。

    @GetMapping("/hello")
    public String sayHello(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new SunException(SystemEnum.PARAM_ILLEGAL);
        }
        return "The sun is rising.";
    }

Swagger测试效果:

SpringBoot优雅地处理全局异常,返回前端_第1张图片

方式二、AOP实现

很明显,aop天生就是干这个的料。aop在业务解耦方面简直如鱼得水,像统一打印日志,统一捕获异常等等。talk is cheap,show me the code.

如下代码所示:

a. 新建这个aop监控类,新增切点,切所有模块的Controller层的所有方法;

b. 实现一个环绕通知接口,打印方法入参以及请求结果;

c. 注意代码40行到45行,就是捕获项目中所有的SunException,并封装成统一的响应体返回前端。

/**
 * aop监控类
 **/
@Slf4j
@Aspect
@Component
public class AspectMonitor {

    /**
     * 日志切点,切所有模块controller层中的所有方法
     */
    @Pointcut("execution(* com.bxbro.*..controller..*.*(..))")
    public void logPointCut() {
        // do nothing.
    }


    /**
     * 对接口做统一的日志及异常处理
     * @param pjp
     * @return
     */
    @Around("logPointCut()")
    public Object apiMonitor(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i=0;i

你可能感兴趣的:(这就叫优雅,Spring,SpringBoot,springboot,aop)