Spring Boot 2.x(七):全局异常处理

Stella981
• 阅读 883

前言

异常的处理在我们的日常开发中是一个绕不过去的坎,在Spring Boot 项目中如何优雅的去处理异常,正是我们这一节课需要研究的方向。

异常的分类

在一个Spring Boot项目中,我们可以把异常分为两种,第一种是请求到达Controller层之前,第二种是到达Controller层之后项目代码中发生的错误。而第一种又可以分为两种错误类型:1. 路径错误 2. 类似于请求方式错误,参数类型不对等类似错误。

Spring Boot 2.x(七):全局异常处理

定义ReturnVO和ReturnCode

为了保持返回值的统一,我们这里定义了统一返回的类ReturnVO,以及一个记录错误返回码和错误信息的枚举类ReturnCode,而具体的错误信息和错误代码保存到了response.properties中,使用流进行读取。

ReturnVO

public class ReturnVO {

    private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL);

    /**
     * 返回代码
     */
    private String code;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 返回数据
     */
    private Object data;


    public Object getData() {
        return data;
    }

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

    public String getMessage() {
        return message;
    }

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

    public String getCode() {
        return code;
    }

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

    /**
     * 默认构造,返回操作正确的返回代码和信息
     */
    public ReturnVO() {
        this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
        this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
    }

    /**
     * 返回代码,这里需要在枚举中去定义
     * @param code
     */
    public ReturnVO(ReturnCode code) {
        this.setCode(properties.getProperty(code.val()));
        this.setMessage(properties.getProperty(code.msg()));
    }

    /**
     * 返回数据,默认返回正确的code和message
     * @param data
     */
    public ReturnVO(Object data) {
        this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
        this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
        this.setData(data);
    }

    /**
     * 返回错误的代码,以及自定义的错误信息
     * @param code
     * @param message
     */
    public ReturnVO(ReturnCode code, String message) {
        this.setCode(properties.getProperty(code.val()));
        this.setMessage(message);
    }

    /**
     * 返回自定义的code,message,以及data
     * @param code
     * @param message
     * @param data
     */
    public ReturnVO(ReturnCode code, String message, Object data) {
        this.setCode(code.val());
        this.setMessage(message);
        this.setData(data);
    }

    @Override
    public String toString() {
        return "ReturnVO{" +
                "code='" + code + '\'' +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

ReturnCode

其他的错误处理只需要在枚举类中添加对应的异常即可,枚举的名称要定义为异常的名称,这样可以直接不用对其他的代码进行修改,添加一个新的异常时,仅仅添加枚举类中的字段和properties文件中的属性。

public enum ReturnCode {

    /** 操作成功 */
    SUCCESS("SUCCESS_CODE", "SUCCESS_MSG"),

    /** 操作失败 */
    FAIL("FAIL_CODE", "FAIL_MSG"),

    /** 空指针异常 */
    NullPointerException("NPE_CODE", "NPE_MSG"),

    /** 自定义异常之返回值为空 */
    NullResponseException("NRE_CODE", "NRE_MSG"),

    /** 运行时异常 */
    RuntimeException("RTE_CODE","RTE_MSG"),

    /** 请求方式错误异常 */
    HttpRequestMethodNotSupportedException("REQUEST_METHOD_UNSUPPORTED_CODE","REQUEST_METHOD_UNSUPPORTED_MSG"),

    /** INTERNAL_ERROR */
    BindException("BIND_EXCEPTION_CODE","BIND_EXCEPTION_MSG"),

    /** 页面路径不对 */
    UrlError("UE_CODE","UE_MSG");

    private ReturnCode(String value, String msg){
        this.val = value;
        this.msg = msg;
    }

    public String val() {
        return val;
    }

    public String msg() {
        return msg;
    }

    private String val;
    private String msg;
}

response.properties

这里我自定义了一些异常用于后面的测试,在我们实际的项目中需要定义很多的异常去完善。

SUCCESS_CODE=2000
SUCCESS_MSG=操作成功

FAIL_CODE=5000
FAIL_MSG=操作失败

NPE_CODE=5001
NPE_MSG=空指针异常

NRE_CODE=5002
NRE_MSG=返回值为空

RTE_CODE=5001
RTE_MSG=运行时异常

UE_CODE=404
UE_MSG=页面路径有误

REQUEST_METHOD_UNSUPPORTED_CODE=4000
REQUEST_METHOD_UNSUPPORTED_MSG=请求方式异常

BIND_EXCEPTION_CODE=4001
BIND_EXCEPTION_MSG=请求参数绑定失败

路径错误处理

这里的路径错误处理方式是采用了实现ErrorController接口,然后实现了getErrorPath()方法:

/**
 * 请求路径有误
 * @author yangwei
 * @since 2019-01-02 18:13
 */
@RestController
public class RequestExceptionHandler implements ErrorController {

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping("/error")
    public ReturnVO errorPage(){
        return new ReturnVO(ReturnCode.UrlError);
    }
}

这里可以进行测试一下:

Spring Boot 2.x(七):全局异常处理

使用ControllerAdvice对其他类型的异常进行处理

类似于到达Controller之前的请求参数错误,请求方式错误,数据格式不对等等错误都归类为一种,这里仅仅展示请求方式错误的处理方式。

/**
 * 全局异常处理类
 * @author yangwei
 *
 * 用于全局返回json,如需返回ModelAndView请使用ControllerAdvice
 * 继承了ResponseEntityExceptionHandler,对于一些类似于请求方式异常的异常进行捕获
 */
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL);

    /**
     * 重写handleExceptionInternal,自定义处理过程
     **/
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        //这里将异常直接传给handlerException()方法进行处理,返回值为OK保证友好的返回,而不是出现500错误码。
        return new ResponseEntity<>(handlerException(ex), HttpStatus.OK);
    }

    /**
     * 异常捕获
     * @param e 捕获的异常
     * @return 封装的返回对象
     **/
    @ExceptionHandler(Exception.class)
    public ReturnVO handlerException(Throwable e) {
        ReturnVO returnVO = new ReturnVO();
        String errorName = e.getClass().getName();
        errorName = errorName.substring(errorName.lastIndexOf(".") + 1);
        //如果没有定义异常,而是直接抛出一个运行时异常,需要进入以下分支
        if (e.getClass() == RuntimeException.class) {
            returnVO.setMessage(properties.getProperty(valueOf("RuntimeException").msg()) +": "+ e.getMessage());
            returnVO.setCode(properties.getProperty(valueOf("RuntimeException").val()));
        } else {
            returnVO.setMessage(properties.getProperty(valueOf(errorName).msg()));
            returnVO.setCode(properties.getProperty(valueOf(errorName).val()));
        }
        return returnVO;
    }
}

这里我们可以进行测试:

@RestController
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private IUserService userService;

    @PostMapping(value = "/findAll")
    public Object findAll() {
        throw new RuntimeException("ddd");
    }

    @RequestMapping(value = "/findAll1")
    public ReturnVO findAll1(UserDO userDO) {
        System.out.println(userDO);
        return new ReturnVO(userService.findAll1());
    }

   @RequestMapping(value = "/test")
    public ReturnVO test() {
        throw new RuntimeException("测试非自定义运行时异常");
    }
}

直接在浏览器访问findAll,默认为get方法,这里按照我们期望会抛出请求方式异常的错误:

Spring Boot 2.x(七):全局异常处理

访问findAll1?id=123ss,这里由于我们接受的UserDOid属性是Integer类型,所以这里报一个参数绑定异常:

Spring Boot 2.x(七):全局异常处理

访问test,测试非自定义运行时异常:

Spring Boot 2.x(七):全局异常处理

结合AOP使用,放入公用模块减少代码的重复

我们上节课使用AOP对于全局异常处理进行了一次简单的操作,这节课进行了完善,并将其放入到我们的公用模块,使用时只需导入jar包,然后在启动类配置扫描包路径即可

/**
 * 统一封装返回值和异常处理
 *
 * @author vi
 * @since 2018/12/20 6:09 AM
 */
@Slf4j
@Aspect
@Order(5)
@Component
public class ResponseAop {

    @Autowired
    private GlobalExceptionHandler exceptionHandler;

    /**
     * 切点
     */
    @Pointcut("execution(public * indi.viyoung.viboot.*.controller..*(..))")
    public void httpResponse() {
    }

    /**
     * 环切
     */
    @Around("httpResponse()")
    public ReturnVO handlerController(ProceedingJoinPoint proceedingJoinPoint) {
        ReturnVO returnVO = new ReturnVO();
        try {
            Object proceed = proceedingJoinPoint.proceed();
            if (proceed instanceof ReturnVO) {
                returnVO = (ReturnVO) proceed;
            } else {
                returnVO.setData(proceed);
            }
        }  catch (Throwable throwable) {
            // 这里直接调用刚刚我们在handler中编写的方法
            returnVO =  exceptionHandler.handlerException(throwable);
        }
        return returnVO;
    }
}

做完这些准备工作,以后我们在进行异常处理的时候只需要进行以下几步操作:

  • 引入公用模块jar包
  • 在启动类上配置扫描包路径
  • 如果新增异常的话,在枚举类中新增后,再去properties中进行返回代码和返回信息的编辑即可(注意:枚举类的变量名一定要和异常名保持一致

公众号

Spring Boot 2.x(七):全局异常处理

原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知!

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
kenx kenx
3年前
SpringBoot优雅的全局异常处理
前言在日常项目开发中,异常是常见的,但是如何更高效的处理好异常信息,让我们能快速定位到BUG,是很重要的,不仅能够提高我们的开发效率,还能让你代码看上去更舒服,SpringBoot的项目已经有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理。SpringBoot默认的错误处理机制返回错误页面默认返回W
Wesley13 Wesley13
3年前
03.Android崩溃Crash库之ExceptionHandler分析
目录总结00.异常处理几个常用api01.UncaughtExceptionHandler02.Java线程处理异常分析03.Android中线程处理异常分析04.为何使用setDefaultUncaughtExceptionHandler前沿上一篇整体介绍了crash崩溃
Stella981 Stella981
3年前
Spring Boot @ControllerAdvice+@ExceptionHandler处理controller异常
需求:  1.springboot 项目restful 风格统一放回json  2.不在controller写trycatch代码块简洁controller层  3.对异常做统一处理,同时处理@Validated校验器注解的异常方法:  @ControllerAdvice注解定义全局异常处理类@ControllerAdvice
Wesley13 Wesley13
3年前
J2EE项目异常处理
       为什么要在J2EE项目中谈异常处理呢?可能许多java初学者都想说:“异常处理不就是try….catch…finally吗?这谁都会啊!”。笔者在初学java时也是这样认为的。如何在一个多层的j2ee项目中定义相应的异常类?在项目中的每一层如何进行异常处理?异常何时被抛出?异常何时被记录?异常该怎么记录?何时需要把checkedExc
Wesley13 Wesley13
3年前
初探 Objective
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言异常处理是许多高级语言都具有的特性,它可以直接中断当前函数并将控制权转交给能够处理异常的函数。不同语言在异常处理的实现上各不相同,本文主要来分析一下ObjectiveC和C这两个语言。为什么要把ObjectiveC和
Stella981 Stella981
3年前
SpringBoot过滤器中的异常处理
在昨天的文章我跟大家分享了SpringBoot中异常的处理中,我说了一个需要注意的点,就是过滤器中抛出的异常无法被异常处理类捕获,然后这个朋友就问应该如何处理。其实处理这种问题的处理方式有好几种,那么我就简单分享一下我近期一个项目中的处理方式。Filter中的异常处理思路首先我们要明白,在过滤器中我们一般是不会写很长
Easter79 Easter79
3年前
SpringBoot过滤器中的异常处理
在昨天的文章我跟大家分享了SpringBoot中异常的处理中,我说了一个需要注意的点,就是过滤器中抛出的异常无法被异常处理类捕获,然后这个朋友就问应该如何处理。其实处理这种问题的处理方式有好几种,那么我就简单分享一下我近期一个项目中的处理方式。Filter中的异常处理思路首先我们要明白,在过滤器中我们一般是不会写很长
Stella981 Stella981
3年前
SpringBoot2 全局异常处理
参考这篇文章里面的几种异常形式:全局异常处理是个比较重要的功能,一般在项目里都会用到。 大概把一次请求分成三个阶段,来分别进行全局的异常处理。 一:在进入Controller之前,譬如请求一个不存在的地址,404错误。 二:在执行@RequestMapping时,进入逻辑处理阶段前。譬如传的参数类型错误。 
Easter79 Easter79
3年前
SpringBoot2 全局异常处理
参考这篇文章里面的几种异常形式:全局异常处理是个比较重要的功能,一般在项目里都会用到。 大概把一次请求分成三个阶段,来分别进行全局的异常处理。 一:在进入Controller之前,譬如请求一个不存在的地址,404错误。 二:在执行@RequestMapping时,进入逻辑处理阶段前。譬如传的参数类型错误。