为什么要有统一异常处理?
现在开发的绝大多数JavaWeb项目都是采用了“前后端分离”的这种形式,前后端通过接口来进行JSON数据的交互。那么首先大家先看看下面的这两张图:
A接口是请求成功的样子,B接口虽然显示的是请求失败,但是也还是把请求的接口状态返回了。然后前端就可以根据这个状态去进行相关逻辑操作,比如成功了话刷新页面,失败了跳转到错误页面或者弹框警告。这样其实可以很大程度的提高用户的体验。
但是!有的时候后端在进行业务处理的时候会报错!比如人尽皆知的NullPointerException
,当后端报了空指针异常时,在没有进行统一异常处理的时候,接口的返回就变成了这个样子:
这是啥玩意,就告诉我服务器出错了?来个status=500
就没了?一点也不友好!连后端到底报啥错也没有,而且这个根本也不是JSON的格式,说好了前后端通过接口来进行JSON数据的交互呢?前端都拿不到JSON
,所以也就没法做业务判断、没法做错误页面跳转、没法做警告弹框。
所以,为了让后端即使出错了,也可以进行正常的JSON数据的返回,顶多就是在JSON里声明后端报错了,然后再把报的什么错告诉前端,然后前端就可以继续做业务处理了,你看,这种多人性化!所以,我们就需要对后端框架的异常报错进行全局的捕获处理。具体怎么做那就往下看吧~
注解@ControllerAdvice
SpringBoot中有一个@ControllerAdvice
的注解,使用该注解即表示开启全局异常捕获,接下来我们只需在自定义的方法上使用@ExceptionHandler注解,并定义捕获异常的类型,那么这个自定义的方法体就是对这种类型的异常进行统一的处理。
同时还提供@RestControllerAdvice
注解,就相当于@RestController
。
@RestController
=@Controller
+@ResponseBody
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * 捕获全局异常,处理所有不可知的异常 */ @ExceptionHandler(value=Exception.class) public ResponseResult handleException(Exception e, HttpServletRequest request) { log.error("出现未知异常 -> ", e); ResponseError error = new ResponseError(); error.setCode("APPEAR_ERROR"); error.setMessage(e.getMessage()); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } /** * 捕获空指针异常 */ @ExceptionHandler(value=NullPointerException.class) public ResponseResult handleNullPointerException(NullPointerException e, HttpServletRequest request) { log.error("出现空指针异常 -> ", e); ResponseError error = new ResponseError(); error.setCode("NULL_POINTER_ERROR"); error.setMessage("出现空指针异常"); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } }
这里要注意一下,统一异常处理会优先处理子类异常,也就是说当出现了空指针异常,那么会优先交给handleNullPointerException
这个方法处理,如果没有明确的针对某种异常进行处理,那么handleException
这个方法会自觉的处理它们。
先看效果
加上统一异常处理之后,接口的返回就变得十分优雅了~
瞅见了吧,现在后端无论是怎么报错,返回给前端的永远是JSON数据,之后前端就可以该跳转错误页面的跳转,该弹框警告的弹框!统一异常处理,永远的神!
完整项目代码可前往公众号【爱学习的莫提】,后台回复【统一异常处理】获取。
具体实现
目录结构
引入POM依赖
<dependencies> <!--引入Web启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入日志启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <!--引入Lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--引入fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <!--引入日志依赖--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!--引入Hutools工具--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
创建接口返回JSON格式
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class ResponseResult { /** * 响应编码 */ protected String code; /** * 响应信息 */ protected String message; /** * 响应数据 */ protected Object data; /** * 请求成功 */ public static ResponseResult success(){ return ResponseResult.builder().code("SUCCESS").message("请求成功").build(); } /** * 请求成功 */ public static ResponseResult success(String message){ return ResponseResult.builder().code("SUCCESS").message(message).build(); } /** * 请求成功 */ public static ResponseResult success(String message, Object data){ return ResponseResult.builder().code("SUCCESS").message(message).data(data).build(); } /** * 请求失败 */ public static ResponseResult fail(){ return ResponseResult.builder().code("FAIL").message("请求失败").build(); } /** * 请求失败 */ public static ResponseResult fail(String message){ return ResponseResult.builder().code("FAIL").message(message).build(); } /** * 请求失败 */ public static ResponseResult fail(String message, Object data){ return ResponseResult.builder().code("FAIL").message(message).data(data).build(); } }
@Data @NoArgsConstructor @AllArgsConstructor public class ResponseError extends ResponseResult{ /** * 请求地址(发生异常时返回) */ private String requestUrl; /** * 异常类(发生异常时返回) */ private String exception; }
自定义异常
@Data @NoArgsConstructor @AllArgsConstructor public class BusinessException extends RuntimeException { /** * 错误码 */ private String code; /** * 错误信息 */ private String message; public BusinessException(String message){ this.code = "BUSINESS_ERROR"; this.message = message; } }
@Data @NoArgsConstructor @AllArgsConstructor public class RequestParamIsNullException extends RuntimeException{ /** * 错误码 */ private String code; /** * 错误信息 */ private String message; }
全局异常处理
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { /** * 捕获全局异常,处理所有不可知的异常 */ @ExceptionHandler(value=Exception.class) public ResponseResult handleException(Exception e, HttpServletRequest request) { log.error("出现未知异常 -> ", e); ResponseError error = new ResponseError(); error.setCode("APPEAR_ERROR"); error.setMessage(e.getMessage()); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } /** * 捕获空指针异常 */ @ExceptionHandler(value=NullPointerException.class) public ResponseResult handleNullPointerException(NullPointerException e, HttpServletRequest request) { log.error("出现空指针异常 -> ", e); ResponseError error = new ResponseError(); error.setCode("NULL_POINTER_ERROR"); error.setMessage("出现空指针异常"); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } /** * 自定义异常处理 */ @ExceptionHandler(value=BusinessException.class) public ResponseResult handleBusinessException(BusinessException e, HttpServletRequest request) { log.error("出现业务异常 -> ", e); ResponseError error = new ResponseError(); error.setCode(e.getCode()); error.setMessage(e.getMessage()); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } /** * 处理参数为空异常 */ @ExceptionHandler(value = RequestParamIsNullException.class) public ResponseResult handleInputParamIsNullException(RequestParamIsNullException e, HttpServletRequest request) { log.error("请求参数为空异常 -> ", e); ResponseError error = new ResponseError(); error.setCode(e.getCode()); error.setMessage(e.getMessage()); error.setRequestUrl(request.getRequestURL().toString()); error.setException(e.getClass().getName()); return error; } }
测试接口
@RestController public class HelloController { /** * 成功 */ @GetMapping("/hello1") public ResponseResult hello1(){ Map<String,Object> map = new HashMap<>(); map.put("name", "莫提"); map.put("sex", "男"); map.put("age", 22); return ResponseResult.success("请求成功", map); } /** * 失败 */ @GetMapping("/hello2") public ResponseResult hello2(){ return ResponseResult.fail("请求失败"); } /** * 触发自定义业务异常 */ @GetMapping("/hello3") public ResponseResult hello3(){ throw new BusinessException("触发自定义业务异常"); } /** * 触发其他异常 */ @GetMapping("/hello4") public ResponseResult hello4(){ int i = 1 / 0; return new ResponseResult(); } /** * 触发其他异常 */ @GetMapping("/hello5") public ResponseResult hello5(){ Integer.parseInt("a"); return new ResponseResult(); } /** * 触发空指针异常 */ @GetMapping("/hello6") public ResponseResult hello6(){ Integer i = null; int value = i.intValue(); return new ResponseResult(); } }