系统开发实践-Spring Cloud 异常封装及传递处理

1085 字
5 分钟
系统开发实践-Spring Cloud 异常封装及传递处理

异常体系定义#

Untitled 53
Untitled 53

异常编码的设计使用#

这里采用了精简异常代码设计的形式

ServiceException业务异常, 异常编码默认使用BIZ_ERROR, 异常文字提示根据实际情况抛出, 例如

private void checkParameter(TApprovalResult result) {
if (Objects.isNull(result)) {
throw new ServiceException("审批结果不能为null");
}
try {
CheckParameterUtil.checkParameter(result);
} catch (Exception e) {
ServiceException exception = new ServiceException("参数校验异常");
exception.addSuppressed(e);
throw exception;
}
}

系统设计上, 会针对业务异常, 日志记录上做 WARN 级别设置

系统异常默认使用SYSTEM_ERROR, 提示系统忙, 请稍后再试, 默认非 ServiceException 的异常, 都归属于系统异常, 需要做告警提示并及时修复

异常类型默认异常 code默认异常 message日志记录级别
ServiceExceptionBIZ_ERRORWARN
SystemExceptionSYSTEM_ERROR系统忙, 请稍后再试ERROR

Spring Boot 异常捕捉扩展#

spring boot 的 ErrorMvcAutoConfiguration 有参考实现, 我们对其进行扩展, 个性化输出内容

@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class CustomErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
private static final String ERROR_ATTRIBUTE = CustomErrorAttributes.class.getName() + ".ERROR";
private String errorPath;
public CustomErrorAttributes(String errorPath) {
this.errorPath = errorPath;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
// 在发生异常时,传递异常类型
response.addHeader(HttpHeaderConstants.HEADER_EXCEPTION_NAME, ex.getClass().getName());
storeErrorAttributes(request, ex);
// 如果是业务异常, 日志级别为 warn
if (ex instanceof ServiceException) {
log.warn(ex.getMessage(), ex);
request.setAttribute(ErrorAttributeConstans.ATTR_STATUS, 500);
request.setAttribute(ErrorAttributeConstans.SERVLET_ERROR_STATUS_CODE, 500);
return new ModelAndView(this.errorPath);
}
return null;
}
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
request.setAttribute(ERROR_ATTRIBUTE, ex);
}
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put(ErrorAttributeConstans.ATTR_TIMESTAMP, new Date());
addStatus(errorAttributes, webRequest);
addErrorCode(errorAttributes, webRequest, getError(webRequest));
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
Integer status = getAttribute(requestAttributes, ErrorAttributeConstans.SERVLET_ERROR_STATUS_CODE);
if (status == null) {
errorAttributes.put(ErrorAttributeConstans.ATTR_STATUS, 999);
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR, "None");
return;
}
errorAttributes.put(ErrorAttributeConstans.ATTR_STATUS, status);
try {
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR, HttpStatus.valueOf(status).getReasonPhrase());
} catch (Exception ex) {
// Unable to obtain a reason
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR, "Http Status " + status);
}
}
private void addErrorCode(Map<String, Object> errorAttributes, RequestAttributes requestAttributes,
Throwable error) {
String code = getAttribute(requestAttributes, ErrorCoded.ATTR_ERROR_CODE);
if (code == null && error instanceof ErrorCoded) {
ErrorCoded errorCoded = (ErrorCoded)error;
code = errorCoded.getErrorCode();
}
if (StringUtils.hasText(code)) {
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR_CODE, code);
}
}
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
boolean includeStackTrace) {
Throwable error = getError(webRequest);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
error = error.getCause();
}
errorAttributes.put(ErrorAttributeConstans.ATTR_EXCEPTION, error.getClass().getName());
addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
Object message = getAttribute(webRequest, ErrorAttributeConstans.ATTR_MESSAGE);
if (message == null) {
message = getAttribute(webRequest, ErrorAttributeConstans.SERVLET_ERROR_MESSAGE);
}
if ((!StringUtils.isEmpty(message)) && !(error instanceof BindingResult)) {
errorAttributes.put(ErrorAttributeConstans.ATTR_MESSAGE,
StringUtils.isEmpty(message) ? "No message available" : message);
}
}
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
BindingResult result = extractBindingResult(error);
if (result == null) {
if (error instanceof ErrorCodedException) {
errorAttributes.put(ErrorAttributeConstans.ATTR_MESSAGE, error.getMessage());
return;
}
errorAttributes.put(ErrorAttributeConstans.ATTR_MESSAGE, "系统忙, 请稍后再试");
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR_MESSAGE, error.getMessage());
return;
}
if (result.getErrorCount() > 0) {
errorAttributes.put(ErrorAttributeConstans.ATTR_ERROR, result.getAllErrors());
errorAttributes.put(ErrorAttributeConstans.ATTR_MESSAGE, getBindingResultMessage(result));
} else {
errorAttributes.put(ErrorAttributeConstans.ATTR_MESSAGE, "参数绑定失败");
}
}
private String getBindingResultMessage(BindingResult result) {
StringBuilder sb = new StringBuilder();
for (ObjectError objectError : result.getAllErrors()) {
if (objectError instanceof FieldError) {
sb = appendFieldError(sb, (FieldError)objectError);
} else {
sb = appendObjectError(sb, objectError);
}
sb.append("\n");
}
return sb.toString();
}
private StringBuilder appendFieldError(StringBuilder sb, FieldError fieldError) {
return sb.append(fieldError.getObjectName()).append(".").append(fieldError.getField()).append(":")
.append(fieldError.getDefaultMessage());
}
private StringBuilder appendObjectError(StringBuilder sb, ObjectError objectError) {
return sb.append(objectError.getObjectName()).append(":").append(objectError.getDefaultMessage());
}
private BindingResult extractBindingResult(Throwable error) {
if (error instanceof BindingResult) {
return (BindingResult)error;
}
if (error instanceof MethodArgumentNotValidException) {
return ((MethodArgumentNotValidException)error).getBindingResult();
}
return null;
}
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
errorAttributes.put(ErrorAttributeConstans.ATTR_TRACE, stackTrace.toString());
}
private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
String path = getAttribute(requestAttributes, ErrorAttributeConstans.SERVLET_ERROR_REQUEST_URI);
if (path != null) {
errorAttributes.put(ErrorAttributeConstans.ATTR_PATH, path);
}
}
@Override
public Throwable getError(WebRequest webRequest) {
Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
if (exception == null) {
exception = getAttribute(webRequest, ErrorAttributeConstans.SERVLET_ERROR_EXCEPTION);
}
return exception;
}
@SuppressWarnings("unchecked")
private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
return (T)requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}
}

同时配合 spring boot 的 AutoConfiguration 机制, 让我们的 CustomErrorAttributes 生效

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
public class CustomErrorMvcAutoConfiguration {
@Value("${server.error.path:${error.path:/error}}")
private String errorPath;
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public CustomErrorAttributes customErrorAttributes() {
return new CustomErrorAttributes(this.errorPath);
}
}

Spring Cloud 的 openfeign 异常传递改造#

spring cloud 微服务, 我们用的 openfeign 声明式调用, 还需要对异常传递进行扩展, 实现在调用方调用服务时, 能够获取服务提供方抛出的异常

@Slf4j
public class ErrorCodedExceptionErrorDecoder implements ErrorDecoder {
private static final String SERVICE_EXCEPTION_NAME = ServiceException.class.getName();
@Override
public Exception decode(String methodKey, Response response) {
int status = response.status();
String exceptionName = null;
Collection<String> list = response.headers().get(HttpHeaderConstants.HEADER_EXCEPTION_NAME);
if (!CollectionUtils.isEmpty(list)) {
for (String s : list) {
if (StringUtils.hasText(s)) {
exceptionName = s;
log.info("捕捉到 feign 异常类型: {}", exceptionName);
break;
}
}
}
if (404 == status) { // 服务 url 不正确, 抛出特殊异常
throw new ServiceNotFoundException(
String.format("Service %s not found for %s", response.request().url(), methodKey));
}
String errorCode = null;
String message = null;
HttpStatus httpStatus = HttpStatus.valueOf(status);
if (httpStatus.is4xxClientError()) {
errorCode = String.valueOf(status);
message = errorCode + ": " + httpStatus.getReasonPhrase();
} else if (httpStatus.is5xxServerError()) {
try {
ErrorBody errorBody = JacksonUtils.jsonToObject(response.body().asInputStream(), ErrorBody.class);
errorCode = errorBody.getErrorCode();
if (errorBody.getErrorMessage() != null) {
message = errorBody.getErrorMessage();
} else {
message = errorBody.getMessage();
}
} catch (IOException e) {
log.error("读取二进制流失败", e);
}
}
if (SERVICE_EXCEPTION_NAME.equalsIgnoreCase(exceptionName)) {
return new ServiceException(message);
} else {
return new SystemException(errorCode, message);
}
}
@Data
private static class ErrorBody implements ErrorCoded {
private String errorCode;
private String message;
private String errorMessage;
}
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

系统开发实践-Spring Cloud 异常封装及传递处理
https://blog.sephy.top/posts/系统开发实践-spring-cloud-异常封装及传递处理/
作者
虾米
发布于
2017-11-08
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
虾米
coder
分类
标签
站点统计
文章
61
分类
4
标签
52
总字数
64,663
运行时长
0
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.13.9
文章许可
CC BY-NC-SA 4.0