2. 处理系统异常
新建一个 GlobalExceptionHandler 全局异常处理类,然后加上 @ControllerAdvice
注解即可拦截项目中抛出的异常,如下:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
// 打印log
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// ……
}
我们点开 @ControllerAdvice
注解可以看到,@ControllerAdvice
注解包含了 @Component
注解,说明在 Spring Boot 启动时,也会把该类作为组件交给 Spring 来管理。除此之外,该注解还有个 basePackages
属性,该属性是用来拦截哪个包中的异常信息,一般我们不指定这个属性,我们拦截项目工程中的所有异常。@ResponseBody
注解是为了异常处理完之后给调用方输出一个 json 格式的封装数据。 在项目中如何使用呢?Spring Boot 中很简单,在方法上通过 @ExceptionHandler
注解来指定具体的异常,然后在方法中处理该异常信息,最后将结果通过统一的 json 结构体返回给调用者。下面我们举几个例子来说明如何来使用。
2.1 处理参数缺失异常
在前后端分离的架构中,前端请求后台的接口都是通过 rest 风格来调用,有时候,比如 POST 请求 需要携带一些参数,但是往往有时候参数会漏掉。另外,在微服务架构中,涉及到多个微服务之间的接口调用时,也可能出现这种情况,此时我们需要定义一个处理参数缺失异常的方法,来给前端或者调用方提示一个友好信息。
参数缺失的时候,会抛出 HttpMessageNotReadableException
,我们可以拦截该异常,做一个友好处理,如下:
/**
* 缺少请求参数异常
* @param ex HttpMessageNotReadableException
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public JsonResult handleHttpMessageNotReadableException(
MissingServletRequestParameterException ex) {
logger.error("缺少请求参数,{}", ex.getMessage());
return new JsonResult("400", "缺少必要的请求参数");
}
我们来写个简单的 Controller 测试一下该异常,通过 POST 请求方式接收两个参数:姓名和密码。
@RestController
@RequestMapping("/exception")
public class ExceptionController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);
@PostMapping("/test")
public JsonResult test(@RequestParam("name") String name,
@RequestParam("pass") String pass) {
logger.info("name:{}", name);
logger.info("pass:{}", pass);
return new JsonResult();
}
}
然后使用 Postman 来调用一下该接口,调用的时候,只传姓名,不传密码,就会抛缺少参数异常,该异常被捕获之后,就会进入我们写好的逻辑,给调用方返回一个友好信息,如下:
2.2 处理空指针异常
空指针异常是开发中司空见惯的东西了,一般发生的地方有哪些呢? 先来聊一聊一些注意的地方,比如在微服务中,经常会调用其他服务获取数据,这个数据主要是 json 格式的,但是在解析 json 的过程中,可能会有空出现,所以我们在获取某个 jsonObject 时,再通过该 jsonObject 去获取相关信息时,应该要先做非空判断。 还有一个很常见的地方就是从数据库中查询的数据,不管是查询一条记录封装在某个对象中,还是查询多条记录封装在一个 List 中,我们接下来都要去处理数据,那么就有可能出现空指针异常,因为谁也不能保证从数据库中查出来的东西就一定不为空,所以在使用数据时一定要先做非空判断。 对空指针异常的处理很简单,和上面的逻辑一样,将异常信息换掉即可。如下:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 空指针异常
* @param ex NullPointerException
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleTypeMismatchException(NullPointerException ex) {
logger.error("空指针异常,{}", ex.getMessage());
return new JsonResult("500", "空指针异常了");
}
}
这个我就不测试了,代码中 ExceptionController 有个 testNullPointException
方法,模拟了一个空指针异常,我们在浏览器中请求一下对应的 url 即可看到返回的信息:
{"code":"500","msg":"空指针异常了"}
2.3 一劳永逸?
当然了,异常很多,比如还有 RuntimeException,数据库还有一些查询或者操作异常等等。由于 Exception 异常是父类,所有异常都会继承该异常,所以我们可以直接拦截 Exception 异常,一劳永逸:
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 系统异常 预期以外异常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleUnexpectedServer(Exception ex) {
logger.error("系统异常:", ex);
return new JsonResult("500", "系统发生异常,请联系管理员");
}
}
但是项目中,我们一般都会比较详细的去拦截一些常见异常,拦截 Exception 虽然可以一劳永逸,但是不利于我们去排查或者定位问题。实际项目中,可以把拦截 Exception 异常写在 GlobalExceptionHandler 最下面,如果都没有找到,最后再拦截一下 Exception 异常,保证输出信息友好。