springBoot 全局异常处理

前言

本demo的github链接: https://github.com/heruideid/springBoot_demo/tree/master
我的公众号「Rui的后端手册」,先赞后看,月入百万

代码分层

springBoot开发的web项目中,强调分层的概念,一个完整的项目一般会划分出controller层和service层。

  • controller层负责**service层业务方法的路由参数的校验**。应用跑起来后,根据收到请求的url和请求类型调用对应controller层的方法处理。controller层方法首先对接收的参数进行校验,包括非空校验、数值范围校验、长度检验等;接着调用service层的方法处理业务逻辑
  • service层负责编写具体的业务代码,比如操作mysql、消息队列、redis等

因此,为了代码的可维护性,controller层代码应该尽量简洁,验证一下参数,直接丢给service层处理即可

简洁的全局异常处理

异常处理的方式无外乎两种:

  • try/catch发现异常,立即处理
  • throw 往上抛出,交给调用者处理

springBoot的web项目中可以采用全局异常处理的方式:

  • 针对项目可能出现的异常,定义一个ServiceException类,该类包括enum类型的变量ExceptionType(代表异常类型)和string类型的变量msg(代表异常信息)

  • service层的方法套一个大的try/catch;捕获到异常后,在日志里打印异常,并往controller层抛出新的ServiceException实例;

  • controller层方法将ServiceException继续抛出

  • 定义一个全局异常处理类,接收并统一处理controller层抛出的ServiceException

  • controller层方法的参数检验通过以下方式:

    • 参数加@Validated注解
    • 在全局异常处理类中定义参数检验发生异常时的处理方法
具体实现

自定义异常:

public enum ExceptionType {

    /**
     * 自定义异常
     */
    REDIS_EXCEPTION(1),
    MYSQL_EXCEPTION(2),
    ILLEGAL_PARAM(3);

    final int code;
    final Level level;

    ExceptionType(int code){
        this.code=code;
        this.level=Level.WARNING;
    }

    ExceptionType(int code,Level level){
        this.code=code;
        this.level=level;
    }

    public int getCode(){
        return code;
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends Exception{
    protected String msg;
    protected ExceptionType exceptionType;
}

自定义ErrorVOCommonJsonResponse,这两个类定义了当出现异常时,返回给调用方的Json数据格式:

@Data
@Slf4j
public class ErrorVO {
    private Date timestamp;
    private Map<String, String[]> params;
    private String desc;

    public ErrorVO(Date timestamp, Map<String, String[]> params, String desc) {
        this.timestamp = timestamp;
        this.params = params;
        this.desc = desc;
    }

    @Override
    public String toString() {
        try {
            return new ObjectMapper().writeValueAsString(this);
        } catch (Exception e) {
            log.warn("toString error.", e);
            return String.format("ErrorDTO{timestamp=%s, params=%s, desc='%s'}",timestamp,params,desc);
        }
    }
}
@Data
public class CommonJsonResponse<T> {
    public static final String MSG_SUCCESS = "SUCCESS";
    public static final String MSG_NOT_FOUND = "NOT FOUND";
    public static final int ERROR_CODE_SUCCESS = 0;
    public static final int ERROR_CODE_NO_SUCH_OBJECT = 404;
    /**
     * 200 OK, 500 internal error, mapping of response code
     */
    private int status;
    /**
     * 0 means success
     */
    private int errorCode;

    /**
     * SUCCESS or others
     */
    private String msg;

    /**
     * the result data, may user info or others
     */
    private T data;

    public CommonJsonResponse() {
    }

    public CommonJsonResponse(int status, int errorCode) {
        this.status = status;
        this.errorCode = errorCode;
    }

    public CommonJsonResponse(int status, int errorCode, String msg) {
        this.status = status;
        this.errorCode = errorCode;
        this.msg = msg;
    }

    public static CommonJsonResponse ok() {
        CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS);
        res.setMsg(CommonJsonResponse.MSG_SUCCESS);
        return res;
    }

    public static<T> CommonJsonResponse ok(T t) {
        CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS);
        res.setMsg(CommonJsonResponse.MSG_SUCCESS);
        res.setData(t);
        return res;
    }
}

controller层写一个全局异常处理类,几个关键点:

  • 在类上加上@ControllerAdvice注解
  • 继承ResponseEntityExceptionHandler,并覆写handleMethodArgumentNotValid方法,这是参数检验失败时的处理方法
  • 自定义一个全局异常处理方法globalExceptionHandler,并加以@ExceptionHandler(ServiceException.class)注解,作用是处理对应controller层方法抛出的指定类型异常
@RestController
@ControllerAdvice
@Slf4j
public class CustomizedExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ServiceException.class)
    public ResponseEntity<CommonJsonResponse> globalExceptionHandler(ServiceException ex, WebRequest request){
        ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(),
                request.getDescription(false));
        ExceptionType exType = ex.getExceptionType();
        if(exType==ExceptionType.REDIS_EXCEPTION||exType==ExceptionType.MYSQL_EXCEPTION){
            // 500 服务器内部错误
            CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(),
                    exType.getCode(), ex.getMsg());
            response.setData(errorDetails);
            return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        else if(exType==ExceptionType.ILLEGAL_PARAM){
            // 400 客户端参数错误
            CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(),
                    exType.getCode(), ex.getMsg());
            response.setData(errorDetails);
            return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.BAD_REQUEST);
        }else {
            // catch as 200
            CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.OK.value(),
                    exType.getCode(), ex.getMsg());
            response.setData(errorDetails);
            return new ResponseEntity<>(response, HttpStatus.OK);
        }
    }

    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        BindingResult exceptions = ex.getBindingResult();

        String message = "参数异常";

        if (exceptions.hasErrors()) {
            List<ObjectError> errors = exceptions.getAllErrors();
            if (!errors.isEmpty()) {
                FieldError fieldError = (FieldError) errors.get(0);
                message = fieldError.getDefaultMessage();
            }
        }
        ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(),
                request.getDescription(false));
        CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(),
                ExceptionType.ILLEGAL_PARAM.getCode(), message);
        response.setData(errorDetails);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
}

controller层在检验参数时加上@Validated注解,这样在校验参数失败时,会调用全局异常处理类中的handleMethodArgumentNotValid方法

@RestController
@RequestMapping("/MyController")
public class MyController {
    @Autowired
    private MyService myService;

    @PostMapping("/test")
    public void test(@Validated @RequestBody Student student) throws ServiceException {
        myService.method1();
    }
}

service层方法就是打印异常日志并向上抛出ServiceException异常:

@Service
@Slf4j
public class MyService {
    public void method1() throws ServiceException {
        try{
            /**
             * your service code
             */
            throw new Exception();
        }catch (Exception e){ //catch各种异常往上抛出对应ServiceException
            log.warn("mysql update exception",e);
            throw new ServiceException("mysql update exception", ExceptionType.MYSQL_EXCEPTION);
        }
    }
}
最后的效果图:

参数非法时:
在这里插入图片描述

参数合法但service层出现异常时:

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值