异常可以说像大宝一样天天见,一不小心就可能写了一个bug.不过有异常不可怕,可怕是出了问题连异常的都没有,一般情况下,我们也都是try catch
的方式在程序里面捕捉,但是这样有时候可能一疏忽,连try catch都忘了写,这时候如果出现问题,就比较尴尬了,所以,我们需要更加方便、优雅的全局异常处理的方式来优化我们的异常捕捉机制。
Spring Boot默认的异常处理方式
这里,我们先看看Spring Boot在我们 程序出现异常的时候,是怎么给我们的提示的。首先,我们得制造一个bug,这是我们最擅长的:
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping("/exception") @RestController @Api(tags = "制造bug") public class ExceptionController {
@GetMapping("err") public String getErr(){ int calc= 1/0; return "error"; } }
|
如果通过丝袜哥来调用我们的接口的话,它已经很体贴的帮我们处理了一下:
可是Spring Boot可就没有丝袜哥那么贴心了:
这样一来,我们得告诉一下Spring Boot,让他也给我们带来贴心的服务,这时候就要提到两个注解:@RestControllerAdvice
和@ExceptionHandler
。
浅析注解
我们先来看看@RestControllerAdvice
和@ExceptionHandler
的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @ControllerAdvice @ResponseBody public @interface RestControllerAdvice { @AliasFor( annotation = ControllerAdvice.class ) String[] value() default {};
@AliasFor( annotation = ControllerAdvice.class ) String[] basePackages() default {};
@AliasFor( annotation = ControllerAdvice.class ) Class<?>[] basePackageClasses() default {};
@AliasFor( annotation = ControllerAdvice.class ) Class<?>[] assignableTypes() default {};
@AliasFor( annotation = ControllerAdvice.class ) Class<? extends Annotation>[] annotations() default {}; }
|
可以看到@RestControllerAdvice
注解继承了@ResponseBody
和@ControllerAdvice
注解,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface ControllerAdvice { @AliasFor("basePackages") String[] value() default {};
@AliasFor("value") String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {}; }
|
@ControllerAdvice
注解又继承了@Component
注解,那么在类上使用@RestControllerAdvice
就会自动被扫描注册到容器中,并且输出json格式.
1 2 3 4 5 6
| @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExceptionHandler { Class<? extends Throwable>[] value() default {}; }
|
@ExceptionHandler
的参数是一个继承了Throwable
的类,比如:Exception
等,也可以自定义Exception
类。
统一返回封装
开发Web Api的时候,返回的数据格式肯定都是固定的,所以,这里需要一个统一的返回格式类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Data public class ResponseResult<T> { //描述 private String msg; //状态码 private int code; //数据 private T data;
public ResponseResult(int code, String msg, T data) { this.msg = msg; this.code = code; this.data = data; }
public static ResponseResult<String> success() { return success(null); }
public static <T> ResponseResult<T> success(T data) { return new ResponseResult<T>(200, "请求成功", data); }
public static <T> ResponseResult<T> error(String msg) { return new ResponseResult<T>(500, msg, null); } }
|
统一异常处理
我们新建一个GlobalExceptionHandler
类,用于处理全局异常.
1 2 3 4 5 6 7 8 9
| @RestControllerAdvice public class GlobalExceptionHandler {
//捕捉所有Exception异常,这里可以捕捉自定义的业务异常 @ExceptionHandler(Exception.class) ResponseResult<?> handleException(Exception ex){ return ResponseResult.error(ex.getMessage()); } }
|
可以看到GlobalExceptionHandler已经为我们处理了Exception
异常,在之前的参数校验中,我们也可以这里统一处理:
1 2 3 4 5 6 7 8 9
| @ExceptionHandler(BindException.class) public ResponseResult<?> handleValidationException(BindException ex) { BindingResult validResult = ex.getBindingResult(); if (validResult.hasErrors()) { return ResponseResult.error(validResult.getAllErrors().get(0).getDefaultMessage()); } else { return ResponseResult.error("操作失败"); } }
|
使用统一处理后,我们的Controller
就更加简洁了:
1 2 3 4 5 6 7 8 9 10
| @RequestMapping("/v1/student") @RestController @Api(tags = "Student API展示") public class StudentController { @PostMapping("/CreateStudent") public ResponseResult<?> CreateStudent(@Valid StudentVO studentVO) { return ResponseResult.success(); } }
|
小插曲
如果在@ExceptionHandler(BindException.class)
捕捉的过程中没有进入到这个异常处理方法中,那么可以debug查看 @ExceptionHandler(Exception.class)
捕捉到的是哪个Exception,再针对这个Exception进行异常处理。