SpringBoot之全局异常处理

异常处理问题分析

异常如何处理

问题引入

针对代码中的异常,常规有两种处理方式,一种throws直接抛出,另一种try..catch捕获。

在java项目中,有可能存在人为逻辑的异常,也可能为取得异常的详情,或是保证程序在异常时继续向下执行,会采用第二种处理方式。

但是,代码中每一处异常都来捕获,会使代码什么冗余且不利于维护。

解决思路

定义一个全局异常处理类,返回统一规范的异常信息;

处理逻辑是,先判定是否会出现异常,再执行后续具体的业务。

业务举例

本文主要为了实现全局异常处理的逻辑,只举简单业务

某公司部门需增加员工,处理流程:1先根据员工编号查询员工对象,2判断员工对象是否有信息,即是否不为空,3若有信息,则说明已存在,无需再添加,若不是,则直接添加。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyService {
// 注入dao层
@Autowired
EmployeeecMapper employeeecMapper;

/**
* 添加员工信息
* @param employee 员工对象
* @return 影响的行数
*/
public int add(Employee employee) {
// 根据id查询员工对象
Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
// 判断是否已有该员工
if (emp != null){
// 已有,抛出异常,异常信息为已有该员工
throw new RuntimeException("异常代码:1201,错误信息:该员工已存在");
}
// 没有,插入该员工
return employeeecMapper.insert(emp);
}
}

异常处理流程

业务中存在运行时异常和业务逻辑异常,前者不运行时很难察觉,后者在遍及业务时就可以定义出来,因此异常分为不可预知异常和可知异常。流程如下:

  1. 自定义全局异常类,使用@ControllerAdvice,控制器增强
  2. 自定义错误代码及错误信息,两种异常最终会采用统一的信息格式来表示,错误代码+错误信息。
  3. 对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。
  4. 不可预知异常通常是由于系统出现bug、或一些外界因素(如网络波动、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。

可知异常

定义异常信息类,变量为错误代码和错误信息,捕获自定义异常时,直接将该对象返回

不可知异常

定义一个map,将常见的异常存入其中,并定义错误代码。对于其他不常见的异常,即map中没有的,同一一个异常对象返回即可。

异常处理代码流程

可知异常

1、定义打印异常信息与返回结果的接口

1
2
3
4
5
6
7
8
9
10
public interface ResultCode {
// 操作是否成功
boolean success();

// 操作结果代码
long code();

// 提示信息
String message();
}
1
2
3
4
public interface Response {
public static final boolean SUCCESS = true;
public static final int SUCCESS_CODE = 10000;
}

2、定义打印异常信息的枚举类和返回结果类

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
32
33
@ToString
public enum CommonCode implements ResultCode {
NO_PAGE(false,404,"没有信息"),
FAIL(false,500,"操作失败!"),
SUCCESS(true,200,"操作成功!");

// 结果信息
boolean success;
long code;
String message;

// 带参构造
CommonCode(boolean success, long code, String message) {
this.success = success;
this.code = code;
this.message = message;
}

@Override
public boolean success() {
return true;
}

@Override
public long code() {
return code;
}

@Override
public String message() {
return message;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@ToString
public class ResponseResult implements Response {

boolean success = SUCCESS;

long code = SUCCESS_CODE;

String message;

public ResponseResult(ResultCode resultCode){
this.success = resultCode.success();
this.code = resultCode.code();
this.message = resultCode.message();
}
}

3、定义错误异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CustomException extends RuntimeException{

@Autowired
ResultCode resultCode;

// 带参构造
public CustomException(ResultCode resultCode){
this.resultCode = resultCode;
}

// getter
public ResultCode getResultCode(){
return resultCode;
}
}

4、定义异常抛出类

1
2
3
4
5
6
7
public class ExceptionCast {

// 静态方法
public static void cast(ResultCode resultCode){
throw new CustomException(resultCode);
}
}

5、定义异常捕获类,使用ControllerAdvice控制器增强的注解,并在捕获CustomException异常的方法上加ExceptionHandler注解,即可捕获该类的所有异常,返回json数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ControllerAdvice 
public class ExceptionCatch {

/**
* 捕获CustomException类异常
* @param customException
* @return 结果信息,json数据
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult customException(CustomException customException){
ResultCode resultCode = customException.getResultCode();
return new ResponseResult(resultCode);
}
}

6、在业务中抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyService {

@Autowired
EmployeeecMapper employeeecMapper;

public int add(Employee employee) {
Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
if (emp != null){
ExceptionCast.cast(CommonCode.FAIL);
}
return employeeecMapper.insert(emp);
}
}

不可知异常处理

1、类似可知异常,先添加错误代码,如

1
UNAUTHORISE(false,510,"没有权限"),

2、在异常捕获类中添加不可知异常的捕获方法。该方法中,定义一个只读的map存储异常类型的错误代码的映射,map中没有的元素,同一用错误代码999来定义。

1
UNKNOWNERROR(false,999,"未知异常"),
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
32
33
34
35
36
37
38
39
40
@ControllerAdvice
public class ExceptionCatch {

// 定义map,存贮常见错误信息。该类map不可修改
private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
// 构建ImmutableMap
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();

@ExceptionHandler(CustomException.class)
@ResponseBody
public ResponseResult customException(CustomException customException){
ResultCode resultCode = customException.getResultCode();
return new ResponseResult(resultCode);
}

/**
* 捕获非自定义类异常
* @param exception
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception exception){
// 记录日志
LOGGER.error("catch exception ==> ",exception.getMessage());
if (EXCEPTIONS == null){
EXCEPTIONS = builder.build();
}
ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
if (resultCode != null){
return new ResponseResult(resultCode);
}else {
return new ResponseResult(CommonCode.UNKNOWNERROR);
}
}

static {
builder.put(HttpMessageNotReadableException.class, CommonCode.INVALID_PARAM);
}
}

完成~~

不可知异常处理

###

-------------本文结束感谢您的阅读-------------

本文标题:SpringBoot之全局异常处理

文章作者:Chet Yuan

发布时间:2019年04月20日 - 17:04

最后更新:2019年04月21日 - 21:04

原始链接:http://chetwhy.github.io/2019/04/20/SpringBoot之全局异常处理/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。