Spring Boot统一异常处理

异常可以说像大宝一样天天见,一不小心就可能写了一个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进行异常处理。

You forgot to set the qrcode for Alipay. Please set it in _config.yml.
You forgot to set the qrcode for Wechat. Please set it in _config.yml.
You forgot to set the business and currency_code for Paypal. Please set it in _config.yml.
You forgot to set the url Patreon. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×