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

异常编码的设计使用
这里采用了精简异常代码设计的形式
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 | 日志记录级别 |
|---|---|---|---|
ServiceException | BIZ_ERROR | 无 | WARN |
SystemException | SYSTEM_ERROR | 系统忙, 请稍后再试 | ERROR |
Spring Boot 异常捕捉扩展
spring boot 的 ErrorMvcAutoConfiguration 有参考实现, 我们对其进行扩展, 个性化输出内容
@Order(Ordered.HIGHEST_PRECEDENCE)@Slf4jpublic 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 声明式调用, 还需要对异常传递进行扩展, 实现在调用方调用服务时, 能够获取服务提供方抛出的异常
@Slf4jpublic 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-异常封装及传递处理/相关文章智能推荐
1
系统开发实践-前后端交互规范
方法论因为要设计新的技术体系架构, 记录一下前后端接口交互规范
2
Spring 异步事件本地消息表设计
技术分享Spring 异步事件与本地消息表结合的设计方案。实现最终一致性的分布式事务解决方案。
3
jasypt-maven-plugin 使用
技术分享mvn jasypt:encrypt-value -Djasypt.encryptor.password="加密用的密码" -Djasypt.plugin.value="需要进行加密的原文"
4
Spring Could 尝鲜记录
技术分享今天是 spring cloud 微服务上线的日子, 记录一下近期的工作成果
5
SpringBoot 在 Docker 容器中的 1 号进程
技术分享踩到了 1 个坑, 发现平滑关机在 docker 容器中没有按照预想的执行
随机文章随机推荐













