Spring Boot 全局异常处理

我们在做 Web 应用的时候,请求处理过程中发生错误异常是一个非常常见的情况,但是异常的处理方式和放回的异常内容并没有做一个统一的处理。在现在这种前后端分离的时代,不论响应成功还是失败的数据格式都需要保持一致。

1. 返回格式统一封装

Api.java

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.xkcoding.scaffold.common;

import com.xkcoding.scaffold.common.status.Status;
import lombok.Data;

import java.io.Serializable;

/**
* <p>
* API 统一返回格式封装
* </p>
*
* @package: com.xkcoding.scaffold.common
* @description: API 统一返回格式封装
* @author: yangkai.shen
* @date: Created in 2018/8/2 下午6:15
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Data
public class Api implements Serializable {

private static final long serialVersionUID = 1672493411204459264L;

/**
* 返回码
*/
private Integer code;

/**
* 返回信息
*/
private String msg;

/**
* 返回数据
*/
private Object data;

/**
* 默认构造
*/
public Api() {
this.code = Status.SUCCESS.getCode();
this.msg = Status.SUCCESS.getMsg();
}

/**
* 构造 API 返回对象
*
* @param code 返回码
* @param msg 返回信息
* @param data 数据
*/
public Api(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}

/**
* 通用构造包含返回信息的 Api <br>
* 主要用于只包含返回信息,不包含数据时的返回
*
* @param code 状态码
* @param msg 信息
* @return Api
*/
public static Api of(int code, String msg, Object data) {
return new Api(code, msg, data);
}

/**
* 通用构造包含返回信息的 Api <br>
* 主要用于只包含返回信息,不包含数据时的返回
*
* @param code 状态码
* @param msg 信息
* @return Api
*/
public static Api ofMessage(int code, String msg) {
return new Api(code, msg, null);
}

/**
* 通用构造包含返回信息的 Api <br>
* 主要用于只包含返回信息,不包含数据时的返回
*
* @param msg 信息
* @return Api
*/
public static Api ofMessage(String msg) {
return new Api(Status.SUCCESS.getCode(), msg, null);
}

/**
* 通用构造包含返回数据的 Api <br>
* 主要用于操作成功时的返回(不带数据)
*
* @return Api
*/
public static Api ofSuccess() {
return new Api(Status.SUCCESS.getCode(), Status.SUCCESS.getMsg(), null);
}

/**
* 通用构造包含返回数据的 Api <br>
* 主要用于操作成功时的返回
*
* @param data 操作成功时需要返回的数据
* @return Api
*/
public static Api ofSuccess(Object data) {
return new Api(Status.SUCCESS.getCode(), Status.SUCCESS.getMsg(), data);
}

/**
* 通过 Status 构造 Api <br>
* 主要用于出现异常时的返回
*
* @param status {@link Status}
* @return Api
*/
public static Api ofStatus(Status status) {
return new Api(status.getCode(), status.getMsg(), null);
}

}

2. 通用状态统一封装

Status.java

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.xkcoding.scaffold.common.status;

import com.xkcoding.scaffold.common.BaseEnum;
import lombok.Getter;

/**
* <p>
* 状态码枚举
* </p>
*
* @package: com.xkcoding.scaffold.common
* @description: 状态码枚举
* @author: yangkai.shen
* @date: Created in 2018/8/2 下午7:49
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public enum Status implements BaseEnum {
/**
* 成功
*/
SUCCESS(200, "成功"),

/**
* 请求错误
*/
BAD_REQUEST(400, "请求错误"),

/**
* 尚未登录
*/
UNAUTHORIZED(401, "尚未登录"),

/**
* 权限不够
*/
FORBIDDEN(403, "权限不够"),

/**
* 请求不存在
*/
REQUEST_NOT_FOUND(404, "请求不存在"),

/**
* 服务器内部错误
*/
INTERNAL_SERVER_ERROR(500, "服务器内部错误"),

/**
* 用户名或密码错误
*/
USERNAME_OR_PASSWORD_ERROR(50000, "用户名或密码错误"),

/**
* 用户不存在
*/
USER_NOT_EXIST(50001, "账号不存在"),

/**
* 用户已被禁用
*/
USER_DISABLE(50002, "账号已被禁用"),

/**
* 账号已删除
*/
USER_DELETED(50003, "账号已删除"),

/**
* 验证码错误
*/
VERIFY_CODE_ERROR(50004, "验证码错误"),

/**
* 登录成功
*/
LOGIN_SUCCESS(50005, "登录成功"),

/**
* 退出成功
*/
LOGOUT_SUCCESS(50006, "退出成功"),

/**
* 验证码异常
*/
CODE_ERROR(50007, "验证码异常"),

/**
* 获取验证码的值失败
*/
CODE_GET_ERROR(50008, "获取验证码的值失败"),

/**
* 验证码的值不能为空
*/
CODE_VALUE_NOT_NULL(50009, "验证码的值不能为空"),

/**
* 验证码不存在
*/
CODE_NOT_FOUND(50010, "验证码不存在"),

/**
* 验证码已过期
*/
CODE_IS_EXPIRED(50011, "验证码已过期"),

/**
* 验证码不匹配
*/
CODE_NOT_MATCH(50012, "验证码不匹配"),

/**
* 验证码发送异常
*/
CODE_SEND_ERROR(50012, "验证码发送异常");

private Integer code;
private String msg;

Status(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

3. 自定义业务异常类

ScaffoldException.java

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.xkcoding.scaffold.exception;

import com.xkcoding.scaffold.common.status.Status;
import lombok.Getter;

/**
* <p>
* 通用全局异常
* </p>
*
* @package: com.xkcoding.scaffold.exception
* @description: 通用全局异常
* @author: yangkai.shen
* @date: Created in 2018/8/2 下午7:59
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Getter
public class ScaffoldException extends Exception {
/**
* 异常码
*/
private Integer code;

/**
* 返回信息
*/
private String msg;

/**
* 返回内容
*/
private Object data;

public ScaffoldException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}

public ScaffoldException(Integer code, String msg, Object data) {
super(msg);
this.code = code;
this.msg = msg;
this.data = data;
}

public ScaffoldException(Status status) {
super(status.getMsg());
this.code = status.getCode();
this.msg = status.getMsg();
}

public ScaffoldException(Status status, Object data) {
super(status.getMsg());
this.code = status.getCode();
this.msg = status.getMsg();
this.data = data;
}
}

4. 配置 Spring Boot

配置 application.yml 主要是为了对404错误作处理

1
2
3
4
5
6
7
spring:
### 开启404异常抛出 ###
mvc:
throw-exception-if-no-handler-found: true
### 关闭静态资源映射 ###
resources:
add-mappings: false

5. 统一异常处理

GlobalExceptionHandler.java

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
41
42
43
44
45
46
47
package com.xkcoding.scaffold.handler;

import com.xkcoding.scaffold.common.Api;
import com.xkcoding.scaffold.common.status.Status;
import com.xkcoding.scaffold.exception.ScaffoldException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
* <p>
* 全局异常处理
* </p>
*
* @package: com.xkcoding.scaffold.handler
* @description: 全局异常处理
* @author: yangkai.shen
* @date: Created in 2018/8/2 下午8:05
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(value = Exception.class)
@ResponseBody
public Api handlerException(Exception e) {
if (e instanceof NoHandlerFoundException) {
log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod());
return Api.ofStatus(Status.REQUEST_NOT_FOUND);
} else if (e instanceof ScaffoldException) {
log.error("【全局异常拦截】ScaffoldException: 状态码 {}, 异常信息 {}", ((ScaffoldException) e).getCode(), e.getMessage());
return new Api(((ScaffoldException) e).getCode(), e.getMessage(), ((ScaffoldException) e).getData());
} else if (e instanceof AccessDeniedException) {
log.error("【全局异常拦截】AccessDeniedException: 异常信息 {}", e.getMessage());
return Api.ofStatus(Status.FORBIDDEN);
}

log.error("【全局异常拦截】: 异常信息 {} ", e.getMessage());
return Api.ofStatus(Status.INTERNAL_SERVER_ERROR);
}
}

Tip

异常处理的时候,务必在全局异常处理类中,打印异常日志打印异常日志打印异常日志

相关代码地址:https://github.com/xkcoding/scaffold

-------------本文结束  感谢您的阅读-------------
xkcoding wechat
欢迎来我的公众号「xkcoding小凯扣丁」逛逛
o(╯□╰)o 赞助一杯咖啡 ~~