为什么要有统一异常处理?
现在开发的绝大多数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();
}
}