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

前言

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

异常的分类

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

定义ReturnVO和ReturnCode

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

ReturnVO

1public class ReturnVO { 2 3    private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL); 4 5    /** 6     * 返回代码 7     */ 8    private String code; 9 10    /** 11     * 返回信息 12     */ 13    private String message; 14 15    /** 16     * 返回数据 17     */ 18    private Object data; 19 20 21    public Object getData() { 22        return data; 23    } 24 25    public void setData(Object data) { 26        this.data = data; 27    } 28 29    public String getMessage() { 30        return message; 31    } 32 33    public void setMessage(String message) { 34        this.message = message; 35    } 36 37    public String getCode() { 38        return code; 39    } 40 41    public void setCode(String code) { 42        this.code = code; 43    } 44 45    /** 46     * 默认构造,返回操作正确的返回代码和信息 47     */ 48    public ReturnVO() { 49        this.setCode(properties.getProperty(ReturnCode.SUCCESS.val())); 50        this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg())); 51    } 52 53    /** 54     * 返回代码,这里需要在枚举中去定义 55     * @param code 56     */ 57    public ReturnVO(ReturnCode code) { 58        this.setCode(properties.getProperty(code.val())); 59        this.setMessage(properties.getProperty(code.msg())); 60    } 61 62    /** 63     * 返回数据,默认返回正确的code和message 64     * @param data 65     */ 66    public ReturnVO(Object data) { 67        this.setCode(properties.getProperty(ReturnCode.SUCCESS.val())); 68        this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg())); 69        this.setData(data); 70    } 71 72    /** 73     * 返回错误的代码,以及自定义的错误信息 74     * @param code 75     * @param message 76     */ 77    public ReturnVO(ReturnCode code, String message) { 78        this.setCode(properties.getProperty(code.val())); 79        this.setMessage(message); 80    } 81 82    /** 83     * 返回自定义的code,message,以及data 84     * @param code 85     * @param message 86     * @param data 87     */ 88    public ReturnVO(ReturnCode code, String message, Object data) { 89        this.setCode(code.val()); 90        this.setMessage(message); 91        this.setData(data); 92    } 93 94    @Override 95    public String toString() { 96        return "ReturnVO{" + 97                "code='" + code + '\'' + 98                ", message='" + message + '\'' + 99                ", data=" + data + 100                '}'; 101    } 102}

ReturnCode

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

1public enum ReturnCode { 2 3    /** 操作成功 */ 4    SUCCESS("SUCCESS_CODE", "SUCCESS_MSG"), 5 6    /** 操作失败 */ 7    FAIL("FAIL_CODE", "FAIL_MSG"), 8 9    /** 空指针异常 */ 10    NullPointerException("NPE_CODE", "NPE_MSG"), 11 12    /** 自定义异常之返回值为空 */ 13    NullResponseException("NRE_CODE", "NRE_MSG"), 14 15    /** 运行时异常 */ 16    RuntimeException("RTE_CODE","RTE_MSG"), 17 18    /** 请求方式错误异常 */ 19    HttpRequestMethodNotSupportedException("REQUEST_METHOD_UNSUPPORTED_CODE","REQUEST_METHOD_UNSUPPORTED_MSG"), 20 21    /** INTERNAL_ERROR */ 22    BindException("BIND_EXCEPTION_CODE","BIND_EXCEPTION_MSG"), 23 24    /** 页面路径不对 */ 25    UrlError("UE_CODE","UE_MSG"); 26 27    private ReturnCode(String value, String msg){ 28        this.val = value; 29        this.msg = msg; 30    } 31 32    public String val() { 33        return val; 34    } 35 36    public String msg() { 37        return msg; 38    } 39 40    private String val; 41    private String msg; 42}

response.properties

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

1SUCCESS_CODE=2000 2SUCCESS_MSG=操作成功 3 4FAIL_CODE=5000 5FAIL_MSG=操作失败 6 7NPE_CODE=5001 8NPE_MSG=空指针异常 9 10NRE_CODE=5002 11NRE_MSG=返回值为空 12 13RTE_CODE=5001 14RTE_MSG=运行时异常 15 16UE_CODE=404 17UE_MSG=页面路径有误 18 19REQUEST_METHOD_UNSUPPORTED_CODE=4000 20REQUEST_METHOD_UNSUPPORTED_MSG=请求方式异常 21 22BIND_EXCEPTION_CODE=4001 23BIND_EXCEPTION_MSG=请求参数绑定失败

路径错误处理

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

1/** 2 * 请求路径有误 3 * @author yangwei 4 * @since 2019-01-02 18:13 5 */ 6@RestController 7public class RequestExceptionHandler implements ErrorController { 8 9    @Override 10    public String getErrorPath() { 11        return "/error"; 12    } 13 14    @RequestMapping("/error") 15    public ReturnVO errorPage(){ 16        return new ReturnVO(ReturnCode.UrlError); 17    } 18}

这里可以进行测试一下:

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

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

1/** 2 * 全局异常处理类 3 * @author yangwei 4 * 5 * 用于全局返回json,如需返回ModelAndView请使用ControllerAdvice 6 * 继承了ResponseEntityExceptionHandler,对于一些类似于请求方式异常的异常进行捕获 7 */ 8@RestControllerAdvice 9public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { 10 11    private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL); 12 13    /** 14     * 重写handleExceptionInternal,自定义处理过程 15     **/ 16    @Override 17    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { 18        //这里将异常直接传给handlerException()方法进行处理,返回值为OK保证友好的返回,而不是出现500错误码。 19        return new ResponseEntity<>(handlerException(ex), HttpStatus.OK); 20    } 21 22    /** 23     * 异常捕获 24     * @param e 捕获的异常 25     * @return 封装的返回对象 26     **/ 27    @ExceptionHandler(Exception.class) 28    public ReturnVO handlerException(Throwable e) { 29        ReturnVO returnVO = new ReturnVO(); 30        String errorName = e.getClass().getName(); 31        errorName = errorName.substring(errorName.lastIndexOf(".") + 1); 32        //如果没有定义异常,而是直接抛出一个运行时异常,需要进入以下分支 33        if (e.getClass() == RuntimeException.class) { 34            returnVO.setMessage(properties.getProperty(valueOf("RuntimeException").msg()) +": "+ e.getMessage()); 35            returnVO.setCode(properties.getProperty(valueOf("RuntimeException").val())); 36        } else { 37            returnVO.setMessage(properties.getProperty(valueOf(errorName).msg())); 38            returnVO.setCode(properties.getProperty(valueOf(errorName).val())); 39        } 40        return returnVO; 41    } 42}

这里我们可以进行测试:

1@RestController 2@RequestMapping(value = "/user") 3public class UserController { 4 5    @Autowired 6    private IUserService userService; 7 8    @PostMapping(value = "/findAll") 9    public Object findAll() { 10        throw new RuntimeException("ddd"); 11    } 12 13    @RequestMapping(value = "/findAll1") 14    public ReturnVO findAll1(UserDO userDO) { 15        System.out.println(userDO); 16        return new ReturnVO(userService.findAll1()); 17    } 18 19   @RequestMapping(value = "/test") 20    public ReturnVO test() { 21        throw new RuntimeException("测试非自定义运行时异常"); 22    } 23}

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

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

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

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

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

1/** 2 * 统一封装返回值和异常处理 3 * 4 * @author vi 5 * @since 2018/12/20 6:09 AM 6 */ 7@Slf4j 8@Aspect 9@Order(5) 10@Component 11public class ResponseAop { 12 13    @Autowired 14    private GlobalExceptionHandler exceptionHandler; 15 16    /** 17     * 切点 18     */ 19    @Pointcut("execution(public * indi.viyoung.viboot.*.controller..*(..))") 20    public void httpResponse() { 21    } 22 23    /** 24     * 环切 25     */ 26    @Around("httpResponse()") 27    public ReturnVO handlerController(ProceedingJoinPoint proceedingJoinPoint) { 28        ReturnVO returnVO = new ReturnVO(); 29        try { 30            Object proceed = proceedingJoinPoint.proceed(); 31            if (proceed instanceof ReturnVO) { 32                returnVO = (ReturnVO) proceed; 33            } else { 34                returnVO.setData(proceed); 35            } 36        }  catch (Throwable throwable) { 37            // 这里直接调用刚刚我们在handler中编写的方法 38            returnVO =  exceptionHandler.handlerException(throwable); 39        } 40        return returnVO; 41    } 42}

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

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

公众号

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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

SpringBoot优雅的全局异常处理

前言在日常项目开发中,异常是常见的,但是如何更高效的处理好异常信息,让我们能快速定位到BUG,是很重要的,不仅能够提高我们的开发效率,还能让你代码看上去更舒服,SpringBoot的项目已经有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理。SpringBoot默认的错误处理机制返回错误页面默认返回W

03.Android崩溃Crash库之ExceptionHandler分析

目录总结00.异常处理几个常用api01.UncaughtExceptionHandler02.Java线程处理异常分析03.Android中线程处理异常分析04.为何使用setDefaultUncaughtExceptionHandler前沿上一篇整体介绍了crash崩溃

Spring Boot @ControllerAdvice+@ExceptionHandler处理controller异常

需求:  1.springboot 项目restful 风格统一放回json  2.不在controller写trycatch代码块简洁controller层  3.对异常做统一处理,同时处理@Validated校验器注解的异常方法:  @ControllerAdvice注解定义全局异常处理类@ControllerAdvice

J2EE项目异常处理

       为什么要在J2EE项目中谈异常处理呢?可能许多java初学者都想说:“异常处理不就是try….catch…finally吗?这谁都会啊!”。笔者在初学java时也是这样认为的。如何在一个多层的j2ee项目中定义相应的异常类?在项目中的每一层如何进行异常处理?异常何时被抛出?异常何时被记录?异常该怎么记录?何时需要把checkedExc