• SpringBoot的全局异常处理


    前言

    本篇文章主要介绍的是SpringBoot的全局异常处理。

    GitHub源码链接位于文章底部。

    首先还是来看工程的结构

    在pom文件中添加相关依赖

     <properties>
    	 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	 <java.version>1.8</java.version>
    	 <maven.compiler.source>1.8</maven.compiler.source>
    	 <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <!--父级依赖,它用来提供相关的 Maven 默认依赖。使用它之后,常用的springboot包依赖可以省去version 标签-->
    <parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-parent</artifactId>
    	<version>2.1.3.RELEASE</version>
    	<relativePath ></relativePath>
    </parent>
    <dependencies>
    	<!-- Spring Boot Web 依赖 核心 -->
    	<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-web</artifactId>
    	</dependency>
    	<!--lombok插件依赖-->
    	<dependency>
    		<groupId>org.projectlombok</groupId>
    		<artifactId>lombok</artifactId>
    		<version>1.16.20</version>
    	</dependency>
    </dependencies>
    

    编码

    Spring Boot的全局异常处理有两个很重要的注解,一个是ControllerAdvice,一个是ExceptionHandler,我们在类上使用ControllerAdvice注解,表示开启了全局异常的捕获;在方法上使用ExceptionHandler注解去捕获具体某一个异常,然后再该方法体内对捕获到的异常进行处理,进行输出、返回等操作。我们来看看下面这个示例是怎么操作的:

    @ControllerAdvice
    public class MyExceptionHandler {
        @ExceptionHandler(value =NullPointerException.class)
        public String exceptionHandler(NullPointerException e){
            System.out.println("发生空指针异常:"+e);
            return e.getMessage();
        }
    	@ExceptionHandler(value = ArithmeticException.class)
    	public String exceptionHandler(ArithmeticException e) {
    		System.out.println("发生数学运算异常:"+e);
    		return e.getMessage();
    	}
    }
    

    上面这个类中就全局捕获了两种具体的异常,我们可以不断地往这个类中添加一些异常。在实际运用过程中,我们会对方法体中捕获的异常进行二次处理,封装成需要的格式,比如一些通用的错误码,返回消息,返回标识等等,这里通过自定义的异常类以及枚举类来实现。

    自定义枚举类

    首先自定义一个枚举类,该枚举类中枚举一些数据操作错误定义。如果有需要,后期可以不断地增加对应枚举情况。

    public enum CommonEnum {
    	// 数据操作错误定义
    	SUCCESS(true,200, "请求成功!"),
    	ERROR(false,201, "请求失败!"),
    	BODY_NOT_MATCH(false,400,"请求的参数错误!"),
    	SIGNATURE_NOT_MATCH(false,401,"请求的数字签名不匹配!"),
    	NOT_FOUND(false,404, "未找到该资源!"),
    	INTERNAL_SERVER_ERROR(false,500, "服务器内部错误!"),
    	SERVER_BUSY(false,503,"服务器正忙,请稍后再试!");
    
    	/** 错误码 */
    	private Integer resultCode;
    	/** 错误描述 */
    	private String resultMsg;
    	private Boolean flag;
    	CommonEnum(Boolean flag, Integer resultCode, String resultMsg) {
    		this.resultCode = resultCode;
    		this.resultMsg = resultMsg;
    		this.flag = flag;
    	}
    
    	/** 返回结果集中用到get方法*/
    	public Boolean getFlag() {
    		return flag;
    	}
    	public Integer getResultCode() {
    		return resultCode;
    	}
    	public String getResultMsg() {
    		return resultMsg;
    	}
    }
    

    自定义一个异常类,用于处理发生的业务异常

    @Data
    public class BizException extends RuntimeException {
    
    	private static final long serialVersionUID = 1L;
    
    	/** 错误码*/
    	protected Integer errorCode;
    	/** 错误信息*/
    	protected String errorMsg;
    	public BizException(Integer errorCode, String errorMsg) {
    		super(errorCode.toString());
    		this.errorCode = errorCode;
    		this.errorMsg = errorMsg;
    	}
    	@Override
    	public String getMessage() {
    		return errorMsg;
    	}
    	@Override
    	public Throwable fillInStackTrace() {
    		return this;
    	}
    }
    

    自定义数据格式

    定义返回的结果集的格式,也就是改造Result类,和枚举类结合起来

    @Data
    public class Result {
        /**
         * 返回结构状态
         */
        private Boolean flag;
    
        /**
         * 返回状态码
         */
        private Integer code;
    
        /**
         * 返回消息
         */
        private String message;
    
        /**
         * 返回数据内容
         */
        private Object data;
    
        public Result(CommonEnum commonEnum, Object data) {
            this.flag = commonEnum.getFlag();
            this.code = commonEnum.getResultCode();
            this.message = commonEnum.getResultMsg();
            this.data = data;
        }
    
        public Result(CommonEnum commonEnum) {
            this.flag = commonEnum.getFlag();
            this.code = commonEnum.getResultCode();
            this.message = commonEnum.getResultMsg();
        }
    
        /**
         * 自定义异常
         */
        public Result(Integer code, String message) {
            this.flag = false;
            this.code = code;
            this.message = message;
        }
    }
    

    自定义全局异常处理类

    最后来自定义全局异常处理类,可以处理自己抛出的自定义的业务异常,也可以处理系统异常,如空指针等,或者一些其他异常。其实就是第一个例子的延伸。

    @ControllerAdvice
    public class GlobalExceptionHandler {
    	private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
    	/**
    	 * 处理自定义的业务异常
    	 * @param e
    	 */
    	@ExceptionHandler(value = BizException.class)
    	@ResponseBody
    	public Result bizExceptionHandler(BizException e) {
    		logger.error("发生业务异常!原因是:{}", e.getErrorMsg());
    		return new Result(e.getErrorCode(), e.getErrorMsg());
    	}
    
    	/**
    	 * 处理空指针的异常
    	 * @param e
    	 */
    	@ExceptionHandler(value = NullPointerException.class)
    	@ResponseBody
    	public Result exceptionHandler(NullPointerException e) {
    		logger.error("发生空指针异常!原因是:", e);
    		return new Result(CommonEnum.BODY_NOT_MATCH,"空指针异常");
    	}
    
    	/**
    	 * 格式转换异常
    	 * @param e
    	 */
    	@ExceptionHandler(value = ParseException.class)
    	@ResponseBody
    	public Result exceptionHandler(ParseException e) {
    		logger.error("发生格式转换异常!原因是:", e);
    		return new Result(CommonEnum.BODY_NOT_MATCH,"格式转换错误");
    	}
    
    	/**
    	 * 方法安全权限异常
    	 * @param e
    	 */
    	@ExceptionHandler(value = IllegalAccessException.class)
    	@ResponseBody
    	public Result exceptionHandler(IllegalAccessException e) {
    		logger.error("发生方法安全权限异常!原因是反射了private方法:", e);
    		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"方法安全权限异常!不能反射private方法");
    	}
    
    	/**
    	 * 数学运算异常
    	 * @param e
    	 * @return
    	 */
    	@ExceptionHandler(value = ArithmeticException.class)
    	@ResponseBody
    	public Result exceptionHandler(ArithmeticException e) {
    		logger.error("发生数学运算异常:", e);
    		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"数学运算异常");
    	}
    
    	/**
    	 * 处理其他异常
    	 * @param e
    	 */
    	@ExceptionHandler(value = Exception.class)
    	@ResponseBody
    	public Result exceptionHandler(Exception e) {
    		logger.error("未知异常!原因是:", e);
    		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,未知异常!);
    	}
    }
    

    logger.error是输出语句,为了在控制台看到结果,等同于System.out,等同于e.printStackTrace()。
    可以看到,这里返回的是之前自定义的结果集,该结果集里定义了多个构造方法,比如new Result(枚举,Obj),new Result(枚举)以及其他,如果需要,可再自行增加。此外,其他遇到的异常也可以在这里按照格式增加。

    创建User实体类

    因为这里不连接数据库,所以只定义简单的属性,pom文件中也没有添加数据库依赖。

    @Data
    public class User implements Serializable {
        /** id */
        private String id;
        /** 姓名 */
        private String name;
        /** 年龄 */
        private Integer age;
    }
    

    UserController控制层

    控制层也使用不同的请求方式,不一样的是,这里请求方式和具体逻辑没有关系,只是为了方便操作api,同时在各个api中故意制造了一些异常,进行测试用,可以参考代码注释。

    @RestController
    @RequestMapping("/user")
    public class UserController {
        @PostMapping
        public Result insert(@RequestBody User user) {
            if (user.getName() == null) {
                //抛出一个自定义异常
                throw new BizException(-1, "用户名不能为空!!!");
            }
            //如果没有异常,则返回数据为上传的user
            return new Result(CommonEnum.SUCCESS,user);
        }
    
        @DeleteMapping
        public Result delete() {
            //制造一个空指针异常,并且不进行处理,会被全局捕获
            String str=null;
            str.equals("test");
            //如果没有异常,则返回请求成功
            return new Result(CommonEnum.SUCCESS);
        }
    
        @PutMapping
        public Result update() {
            //这里故意造成数字异常,并且不进行处理
            int i = 1 / 0;
            //如果没有异常,则返回请求成功,返回数据为i
            return new Result(CommonEnum.SUCCESS,i);
        }
    
        @GetMapping
        public Result find() {
            /**
             * 这里故意造成索引越界异常,并且不进行处理,
             * 因为没有进行处理,全局异常中也没有进行具体异常的捕获
             * 所以被最后的Exception捕获了
             */
            List<String> list = new ArrayList<>();
            String str = list.get(0);
            //如果没有异常,则返回请求成功,返回数据为str
            return new Result(CommonEnum.SUCCESS,str);
        }
    }
    
    

    程序入口

    @SpringBootApplication
    public class ExceptionHandlerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ExceptionHandlerApplication.class, args);
        }
    }
    

    配置文件

    在application.yml中设置访问端口

    server:
      port: 8080
    

    测试

    在postman中调用接口测试

    捕获自定义的业务异常(post方法)


    这里没有传入name参数,所以捕获了自己抛出的自定义异常
    在controller中的post方法中throw new BizException(-1, "用户名不能为空!!!");,可以看到输出了我们封装的返回结果。这里的错误码和错误信息可以自己修改。


    在传入name参数后,没有产生异常,自然也无从捕获,此时返回的结果集正常为传入的user对象,id和age没有传入,因此为null。

    捕获系统异常(delete、put方法)



    因为没有传入参数,这里的body就不截出来了。

    可以看到,这里的flag,code,message,data的值其实都是全局异常类中封装的结果集,前三项是枚举类中的项。如delete方法出现空指针异常,返回的是new Result(CommonEnum.BODY_NOT_MATCH,"空指针异常");这里的CommonEnum.BODY_NOT_MATCH定义了flag,code,message的值,data就是"空指针异常"。
    可以在枚举类中增加NullPointer(false,203,"空指针异常"),然后在全局异常中返回new Result(CommonEnum.NullPointer,"空指针异常"),返回的结果集就是

    {
        "flag": false,
        "code": 203,
        "message": "空指针异常",
        "data": "空指针异常"
    }
    

    put方法同理。

    捕获未在全局异常类中定义的其他异常(get方法)


    这里是捕获了索引越界异常,但是因为全局异常类中没有捕获该具体异常,因此被最后一个Exception异常捕获到了。
    因为这里使用了logger.error进行输出,因此可以在控制台中看到该具体异常,将其按照上面的格式加入到全局异常处理类中,下次再遇到这种异常就能顺利捕获了。

    本文GitHub源码:https://github.com/lixianguo5097/springboot/tree/master/springboot-exceptionHandler

    CSDN:https://blog.csdn.net/qq_27682773
    简书:https://www.jianshu.com/u/e99381e6886e
    博客园:https://www.cnblogs.com/lixianguo
    个人博客:https://www.lxgblog.com

  • 相关阅读:
    Linux操作系统基础(完结)
    单台主机上DB2 10.5和arcgis 10.4 空间数据库配置
    java连接oracle数据库使用SERVICE NAME、SID以及TNSName不同写法
    arcgis desktop 地理编码服务发布
    利用arcgis发布综合又详细的地理定位服务
    centos 安装arcgis server 10.1
    spark源代码
    bootstrap.memory_lock: true导致Elasticsearch启动失败问题
    安装jdk后出现bash: ./java: /lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录
    pyspark采用python3开发
  • 原文地址:https://www.cnblogs.com/lixianguo/p/12518984.html
Copyright © 2020-2023  润新知