前言

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



公众号


友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信