人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道得越多,圆圈也就越大,你不知道的也就越多。

0%

后端框架-统一异常处理

在一个服务开发框架当中,统一异常处理(Error Handling)可以说是最基础的框架功能。

如果没有统一异常处理,一方面无法规范开发人员的异常处理逻辑,另一方面由于异常处理的不规范,也会创造排查问题时效率低下,所以一般企业级服务框架都会对异常处理进行统一封装,并尽可能地减少开发人员手动去处理异常的机会。

在 Spring 中,通过使用 @RestControllerAdvice 和 @ExceptionHandler 这两个注解,我们可以很容易的实现统一异常处理。

工作原理

下图演示了 @RestControllerAdvice 是如何工作的。

RestControllerAdvice工作原理图

代码示例

GlobalExceptionTranslator:

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
@Slf4j
@RestControllerAdvice
public class GlobalExceptionTranslator {

/**
* 请求读取错误
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public BaseResponse handleError(HttpMessageNotReadableException e) {
log.error("Message Not Readable", e);
return BaseResponse
.builder()
.code(ResultCode.MSG_NOT_READABLE)
.message(e.getMessage())
.build();
}

/**
* 请求类型不支持错误
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public BaseResponse handleError(HttpRequestMethodNotSupportedException e) {
log.error("Request Method Not Supported", e);
return BaseResponse
.builder()
.code(ResultCode.METHOD_NOT_SUPPORTED)
.message(e.getMessage())
.build();
}

/**
* MIME 类型不支持错误
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public BaseResponse handleError(HttpMediaTypeNotSupportedException e) {
log.error("Media Type Not Supported", e);
return BaseResponse
.builder()
.code(ResultCode.MEDIA_TYPE_NOT_SUPPORTED)
.message(e.getMessage())
.build();
}

/**
* 参数缺少错误
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public BaseResponse handleError(MissingServletRequestParameterException e) {
log.warn("Missing Request Parameter", e);
String message = String.format("Missing Request Parameter: %s", e.getParameterName());
return BaseResponse
.builder()
.code(ResultCode.PARAM_MISS)
.message(message)
.build();
}

/**
* 参数类型错误
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public BaseResponse handleError(MethodArgumentTypeMismatchException e) {
log.warn("Method Argument Type Mismatch", e);
String message = String.format("Method Argument Type Mismatch: %s", e.getName());
return BaseResponse
.builder()
.code(ResultCode.PARAM_TYPE_ERROR)
.message(message)
.build();
}

/**
* 参数绑定错误
*/
@ExceptionHandler(BindException.class)
public BaseResponse handleError(BindException e) {
log.warn("Bind Exception", e);
FieldError error = e.getFieldError();
String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_BIND_ERROR)
.message(message)
.build();
}

/**
* 参数校验错误
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseResponse handleError(MethodArgumentNotValidException e) {
log.warn("Method Argument Not Valid", e);
BindingResult result = e.getBindingResult();
FieldError error = result.getFieldError();
String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_VALID_ERROR)
.message(message)
.build();
}

/**
* 参数校验错误
*/
@ExceptionHandler(ConstraintViolationException.class)
public BaseResponse handleError(ConstraintViolationException e) {
log.warn("Constraint Violation", e);
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
String message = String.format("%s:%s", path, violation.getMessage());
return BaseResponse
.builder()
.code(ResultCode.PARAM_VALID_ERROR)
.message(message)
.build();
}

/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public BaseResponse handleError(ServiceException e) {
log.error("Service Exception", e);
return BaseResponse
.builder()
.code(e.getResultCode())
.message(e.getMessage())
.build();
}

/**
* 未授权错误
*/
@ExceptionHandler(PermissionDeniedException.class)
public BaseResponse handleError(PermissionDeniedException e) {
log.error("Permission Denied", e);
return BaseResponse
.builder()
.code(e.getResultCode())
.message(e.getMessage())
.build();
}

/**
* 404 错误
*/
@ExceptionHandler(NoHandlerFoundException.class)
public BaseResponse handleError(NoHandlerFoundException e) {
log.error("404 Not Found", e);
return BaseResponse
.builder()
.code(ResultCode.NOT_FOUND)
.message(e.getMessage())
.build();
}


/**
* 500 错误
*/
@ExceptionHandler(Throwable.class)
public BaseResponse handleError(Throwable e) {
log.error("Internal Server Error", e);
return BaseResponse
.builder()
.code(ResultCode.INTERNAL_SERVER_ERROR)
.message(e.getMessage())
.build();
}
}

ResultCode:

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
@Getter
@AllArgsConstructor
public enum ResultCode {
/**
* 操作成功
*/
SUCCESS(HttpServletResponse.SC_OK, "Operation is Successful"),

/**
* 操作失败
*/
FAILURE(HttpServletResponse.SC_BAD_REQUEST, "Business Exception"),

/**
* 请求读取错误
*/
MSG_NOT_READABLE(HttpServletResponse.SC_BAD_REQUEST, "Message Can't be Read"),

/**
* 请求类型不支持错误
*/
METHOD_NOT_SUPPORTED(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method Not Supported"),

/**
* MIME 类型不支持错误
*/
MEDIA_TYPE_NOT_SUPPORTED(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "Media Type Not Supported"),

/**
* 参数缺少错误
*/
PARAM_MISS(HttpServletResponse.SC_BAD_REQUEST, "Missing Required Parameter"),

/**
* 参数类型错误
*/
PARAM_TYPE_ERROR(HttpServletResponse.SC_BAD_REQUEST, "Parameter Type Mismatch"),

/**
* 参数绑定错误
*/
PARAM_BIND_ERROR(HttpServletResponse.SC_BAD_REQUEST, "Parameter Binding Error"),

/**
* 参数校验错误
*/
PARAM_VALID_ERROR(HttpServletResponse.SC_BAD_REQUEST, "Parameter Validation Error"),

/**
* 未授权错误
*/
UN_AUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "Request Unauthorized"),

/**
* 请求拒绝错误
*/
REQ_REJECT(HttpServletResponse.SC_FORBIDDEN, "Request Rejected"),

/**
* 404 错误
*/
NOT_FOUND(HttpServletResponse.SC_NOT_FOUND, "404 Not Found"),

/**
* 500 错误
*/
INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");

/**
* 错误码
*/
final int code;

/**
* 错误提示
*/
final String msg;
}

ServiceException:

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
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 2359767895161832954L;

@Getter
private final ResultCode resultCode;

public ServiceException(String message) {
super(message);
this.resultCode = ResultCode.FAILURE;
}

public ServiceException(ResultCode resultCode) {
super(resultCode.getMsg());
this.resultCode = resultCode;
}

public ServiceException(ResultCode resultCode, String msg) {
super(msg);
this.resultCode = resultCode;
}

public ServiceException(ResultCode resultCode, Throwable cause) {
super(cause);
this.resultCode = resultCode;
}

public ServiceException(String msg, Throwable cause) {
super(msg, cause);
this.resultCode = ResultCode.FAILURE;
}

/**
* for better performance
*/
@Override
public Throwable fillInStackTrace() {
return this;
}

public Throwable doFillInStackTrace() {
return super.fillInStackTrace();
}
}

PermissionDeniedException:

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
public class PermissionDeniedException extends RuntimeException {

@Getter
private final ResultCode resultCode;

public PermissionDeniedException(String message) {
super(message);
this.resultCode = ResultCode.UN_AUTHORIZED;
}

public PermissionDeniedException(ResultCode resultCode) {
super(resultCode.getMsg());
this.resultCode = resultCode;
}

public PermissionDeniedException(ResultCode resultCode, Throwable cause) {
super(cause);
this.resultCode = resultCode;
}

/**
* for better performance
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
}
小礼物走一走,来 Github 关注我