前言

异常的处理在我们的日常开发中是一个绕不过去的坎,在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中进行返回代码和返回信息的编辑即可(注意:枚举类的变量名一定要和异常名保持一致

公众号

Spring Boot 2.x(七):优雅的处理异常的更多相关文章

  1. Spring Boot 2.X(七):Spring Cache 使用

    Spring Cache 简介 在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 ...

  2. Spring Boot系列:七、 实现Mybatis多数据源切换

    一.引入相关maven配置 mybatis;  mysql驱动:jdbc <dependency> <groupId>org.mybatis.spring.boot</g ...

  3. spring boot(六):如何优雅的使用mybatis

    *:first-child{margin-top: 0 !important}.markdown-body>*:last-child{margin-bottom: 0 !important}.m ...

  4. Spring Boot(六):如何优雅的使用 Mybatis

    *:first-child{margin-top: 0 !important}.markdown-body>*:last-child{margin-bottom: 0 !important}.m ...

  5. Spring Boot 2.X 如何优雅的解决跨域问题?

    一.什么是源和跨域 源(origin)就是协议.域名和端口号.URL由协议.域名.端口和路径组成,如果两个URL的协议.域名和端口全部相同,则表示他们同源.否则,只要协议.域名.端口有任何一个不同,就 ...

  6. (转)Spring Boot(六):如何优雅的使用 Mybatis

    http://www.ityouknow.com/springboot/2016/11/06/spring-boot-mybatis.html 这两天启动了一个新项目因为项目组成员一直都使用的是 My ...

  7. Spring Boot 2.0(七):Spring Boot 如何解决项目启动时初始化资源

    在我们实际工作中,总会遇到这样需求,在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等.今天就给大家介绍一个 Spring Boot 神器,专门帮助大家解决项目启动初始化资 ...

  8. 学习Spring Boot:(七)集成Mybatis

    前面都是用的是spring data JPA,现在学习下Mybatis,而且现在Mybatis也像JPA那样支持注解形式了,也非常方便,学习一下. 数据库 mysql 5.7 添加依赖 在pom文件中 ...

  9. Spring Boot 入门(七):集成 swagger2

    本片文章是基于前一篇写的,<Spring Boot 入门(六):集成 treetable 和 zTree 实现树形图>,本篇主要介绍了spring boot集成swagger2.关于swa ...

  10. spring Boot 学习(七、Spring Boot与开发热部署)

    一.热部署在开发中我们修改一个Java文件后想看到效果不得不重启应用,这导致大量时间 花费,我们希望不重启应用的情况下,程序可以自动部署(热部署).有以下四 种情况,如何能实现热部署.•1.模板引擎 ...

随机推荐

  1. VB6进行GZIP解压&C#进行GZIP压缩和解压

    VB进行GZIP解压的,DLL是系统的,如果没有 [点击下载] Option Explicit 'GZIP API '----------------------------------------- ...

  2. 初学angular项目中遇到的一些问题

    1.当angular渲染完成后操作DOM树方法 //当数据渲染完毕 ngApp.directive('repeatFinish', function () {            return {  ...

  3. 可道云kodexplorer网盘未清理造成linux服务器爆满的解决方法

    今天登陆宝塔面板的时候发现硬盘占用37GB,已经变红提示我空间不足了,惊呆了, 还以为是宝塔抽风了,去远程连接服务器看了一下,懵逼了. df -h 查看挂载目录使用情况 还是不相信现实的我又重启了一下 ...

  4. java中class文件与jar文件

    1. JAR 文件包 JAR 文件就是 Java Archive File,顾名思意,它的应用是与 Java 息息相关的,是 Java 的一种文档格式.JAR 文件非常类似 ZIP 文件——准确的说, ...

  5. cookie跟session自我介绍

    Cookie是什么? cookie说的直白点就是保存在用户浏览器端的一个键值对,举个例子,你现在登录了京东商城,你把浏览器关闭之后,你再打开京东,你还是可以对你的账户继续操作,已经购买的商品,订单都是 ...

  6. Mybatis中的Caused by: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 找不到Mapper.xml文件的问题

    1 首先在配置mapper-locations的时候: classpath:  只在现有目录下寻找配置文件 classpath*: 在现有的项目目录下和依赖的jar包下寻找xml配置文件

  7. python3脚本打开摄像头

    openCamera 脚本地址:https://github.com/Mrlshadows/openCamera Mac OS 安装 OpenCV Python 环境为 python3 终端执行如下指 ...

  8. python语法_模块_re(正则表达)

    字符串操作方法有: s = 'hello word' s.find('l') 查询第一个l的索引 s.replace('ll','xx') 替换 s.split('w') 以w进行分割 这些都是完全匹 ...

  9. lua语言自学知识点----简单了解

    零碎知识点: lua:用lua写UI,更新UI,因为lua可直接跨平台解析,不需要编译,方便更新------>热更新. c#反射也可以达到更新,但非常麻烦,切不支持iOS. 在lua中一个人汉字 ...

  10. Chrome+postman+postman interceptor调试

    本文使用chrome+postman4.8.3+postman interceptor0.2.23调试使用cookie的请求. postman4.8.3下载地址:https://pan.baidu.c ...