SpringBoot使用@ControllerAdvice注解做全局异常处理

SpringBoot对Controller层做全局异常处理记录

开发环境

  • JDK1.8
  • SpringBoot 2.3
  • MongoDB 4.2

PS:该示例仅能处理 Controller 层未捕获(往外抛)的异常,不能处理Interceptor(拦截器)层的异常,Spring框架层的异常

封装统一API返回结果

package com.lyc.common;

import com.lyc.util.DateTimeUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 * @author 茶轴的青春
 * @Description 统一API响应结果封装
 * @date 2020/7/22 18:46
 */
@ToString
@NoArgsConstructor
@Data
public class R<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private int Code;
    private boolean Success;
    private T data;
    private String msg;
    private String respTime = DateTimeUtil.getNowTimeForString();

}

创建返回体报文的实体类

package com.lyc.exception;

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import java.time.LocalDateTime;

/**
 * @author 茶轴的青春
 * @Description 异常处理实体
 * @date 2020/7/22 11:20
 */
@Document(collection = "exceptionLog")
@Data
public class LogException {

    /**
     *
     */
    private static final long serialVersionUID = 4531499444309419351L;

    @Field("error_code")
    private int errorCode;

    @Field("msg")
    private String msg;

    @Field("error_log")
    private String errorLog;
    
    @Field("ip")
    private String ip;

    @Field("operate_time")
    private LocalDateTime operateTime;


}

创建异常实体 继承RuntimeException获取异常信息

一般系统抛出的错误是不含错误代码的,除去部分的404,400,500错误之外,我们如果想把错误代码定义的更细致,就需要自己继承RuntimeException这个类后重新定义一个构造方法来定义我们自己的错误信息

package com.lyc.exception;

/**
 * @author 茶轴的青春
 * @Description 异常信息捕获
 * @date 2020/7/22 11:25
 */
public class BusinessException extends RuntimeException {
    private static final long serialVersionUID = 3034121940056795549L;
    private Integer code;

    public Integer getCode() {
        return code;
    }

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

    public BusinessException() {
    }

    public BusinessException(BusinessStatus status) {
        super(status.getMessage());
        this.code = status.getCode();
    }

    public BusinessException(String message) {
        super(message);
        this.code = 500;
    }

    public BusinessException(String message, Integer code) {
        super(message);
        this.code = code;
    }
}

创建一个枚举类 自定义异常信息

根据需求自定义已知的异常信息

package com.lyc.exception;

/**
 * @author 茶轴的青春
 * @Description 全局业务状态码
 * @date 2020/7/22 11:25
 */
public enum BusinessStatus {
    /**
     * 管理员 - 账号密码错误
     */
    ADMIN_PASSWORD(100101, "账号密码错误"),
    /**
     * 未知错误
     */
    UNKNOWN(-1, "未知错误"),
    /**
     * 请求成功
     */
    OK(20000, "成功"),
    /**
     * 请求失败
     */
    FAIL(20001, "失败"),
    /**
     * 熔断请求
     */
    BREAKING(20002, "熔断"),
    /**
     * 非法请求
     */
    ILLEGAL_REQUEST(50000, "非法请求"),
    /**
     * 非法令牌
     */
    ILLEGAL_TOKEN(50008, "非法令牌"),
    /**
     * 其他客户登录
     */
    OTHER_CLIENTS_LOGGED_IN(50012, "其他客户登录"),
    /**
     * 令牌已过期
     */
    TOKEN_EXPIRED(50014, "令牌已过期"),
    /**
     * 用户名已存在
     */
    USER_NAME_EXISTS(50015, "用户名已存在"),

    /**
     * 用户锁定
     */
    LOCK_USER(50017, "用户被锁定"),

    /**
     * 代表不存在
     */
    FUND_SALES_USER_NOT_EXISTS(70001, "代表不存在"),

    /**
     * 没有获取到Token/Token过期
     */
    JD_TOKEN_NOT_EXISTS(80001);
    private Integer code;
    private String message;

    BusinessStatus(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    BusinessStatus(Integer code) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public static String getMessage(int code) {
        for (BusinessStatus status : values()) {
            if (status.getCode().equals(code)) {
                return status.getMessage();
            }
        }
        return null;
    }
}

自定义全局异常处理

@ExceptionHandler可以接受请求处理方法抛出的异常。但是他的作用范围仅限于当前控制器里的方法,而@ControllerAdvice注解,可以帮助我们将其应用到所有的控制器上。

本文示例使用MongoDB存储日志,简单使用可参考SpringBoot集成MongoDB

package com.lyc.exception;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.json.JSONUtil;
import com.lyc.common.R;
import com.lyc.util.GetClientIp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author 茶轴的青春
 * @Description 全局业务异常处理
 * @date 2020/7/22 11:25
 */
@Slf4j
@ControllerAdvice
public class BusinessExceptionHandler {

    @Resource
    private MongoTemplate mongoTemplate;

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<?> handlerException(HttpServletRequest request, Exception ex) {
        R error = new R();
        // 业务异常
        if (ex instanceof BusinessException) {
            error.setCode(((BusinessException) ex).getCode());
            error.setMsg(ex.getMessage());
            log.warn("[全局业务异常]\r\n业务编码:{}\r\n异常记录:{}", error.getCode(), error.getMsg());
        }
        // 未知错误
        else {
            error.setCode(BusinessStatus.UNKNOWN.getCode());
            error.setMsg(BusinessStatus.UNKNOWN.getMessage());
        }
        ex.printStackTrace();
        this.saveLog(error, request, ex);
        return new ResponseEntity<>(error, HttpStatus.OK);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> handlerViolationException(HttpServletRequest request, Exception ex) {
        R error = new R();
        List<String> errorMsgList = ListUtil.list(false);
        if (ex instanceof ConstraintViolationException) {
            error.setCode(500);
            String errorMsg = ex.getMessage();  
            String[] errorArr = errorMsg.split(",");
            for (String errorM : errorArr) {
                String[] msgArr = errorM.split(":");
                if (msgArr != null && msgArr.length >= 2) {
                    errorMsgList.add(msgArr[1]);
                }
            }
            error.setMsg(JSONUtil.toJsonStr(errorMsgList));
            log.warn("[全局业务异常]\r\n业务编码:{}\r\n异常记录:{}", error.getCode(), error.getMsg());
        }
        // 未知错误
        else {
            error.setCode(BusinessStatus.UNKNOWN.getCode());
            error.setMsg(BusinessStatus.UNKNOWN.getMessage());
        }
        ex.printStackTrace();
        this.saveLog(error, request, ex);
        return new ResponseEntity<>(error, HttpStatus.OK);
    }

    /**
     * 保存错误日志
     *
     * @param error
     * @param request
     * @param ex
     */
    private void saveLog(R error, HttpServletRequest request, Exception ex) {
        try {
            LogException logException = new LogException();
            logException.setErrorCode(error.getCode());
            logException.setMsg(ex.getLocalizedMessage());
            logException.setErrorLog(getExceptionAllInformation(ex));
            logException.setIp(GetClientIp.getIpAddress(request));
//            logException.setUserId(SecurityUtil.);
            logException.setOperateTime(LocalDateTime.now());
            ex.getLocalizedMessage();
            log.info(ex.getLocalizedMessage());
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mongoTemplate.save(logException);
                }
            }).start();

        } catch (Exception e) {
            log.info("日志保存失败");
        }
    }

    public static String getExceptionAllInformation(Exception ex) {
        String sOut = "";
        StackTraceElement[] trace = ex.getStackTrace();
        for (StackTraceElement s : trace) {
            sOut += "\tat " + s + "\r\n";
        }
        return sOut;
    }
}

测试

手动定义异常测试

    public R queryUserSystemById() {
        int a = 0 / 0;
        return new R();
    }

后端返回错误 java.lang.ArithmeticException: / by zero

响应结果

{
	"data": null,
	"msg": "未知错误",
	"respTime": "2020-05-27 17:05:18",
	"code": -1,
	"success": false
}

仅做记录 不足之处 请多指正 一起学习 互相进步!

你可能感兴趣的:(SpringBoot,java,spring,boot,后端)