Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 4469|回复: 0

Spring Boot 2.x(七):优雅的处理异常

[复制链接]
  • TA的每日心情
    奋斗
    2025-3-18 14:43
  • 签到天数: 805 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    73万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    731050
    发表于 2021-4-18 20:49:17 | 显示全部楼层 |阅读模式

    前言

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

    异常的分类

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

    定义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);
        }
    }
    

    这里可以进行测试一下:

    使用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方法,这里按照我们期望会抛出请求方式异常的错误:

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

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

    结合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中进行返回代码和返回信息的编辑即可(注意:枚举类的变量名一定要和异常名保持一致

    公众号

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-4-21 19:09 , Processed in 0.066706 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表