青岛个人接网站建设,jsp网站维护,郑州网站建设老牌公司,免费cad图纸下载网站#x1f4e2; 大家好#xff0c;我是 【战神刘玉栋】#xff0c;有10多年的研发经验#xff0c;致力于前后端技术栈的知识沉淀和传播。 #x1f497; #x1f33b; CSDN入驻不久#xff0c;希望大家多多支持#xff0c;后续会继续提升文章质量#xff0c;绝不滥竽充数… 大家好我是 【战神刘玉栋】有10多年的研发经验致力于前后端技术栈的知识沉淀和传播。 CSDN入驻不久希望大家多多支持后续会继续提升文章质量绝不滥竽充数如需交流欢迎留言评论。 文章目录 写在前面的话RestControllerAdvice 实现异常处理基础使用注解简介实战分析 ResponseBodyAdvice 实现返回值包装技术说明实战分析其他方式 总结陈词 写在前面的话
此篇博文继续介绍框架封装过程中关于统一异常处理和返回值包装的具体方案这本是一个相对常见的需求场景此处结合实战情况说明各位看官可一睹为快。 技术栈后端 SpringCloud 前端 Vue/Nuxt RestControllerAdvice 实现异常处理
基础使用
由于场景较简单也不构思了可以直接实现再来考虑内容。 由于是 SpringBoot 项目直接使用注解RestControllerAdvice的方式实现全局异常处理类。 先上一段示例代码
RestControllerAdvice
Slf4j
public class GlobalExceptionHandler {ExceptionHandler(value Throwable.class)public ResultModel jsonErrorHandler(HttpServletRequest req, Throwable e) throws Exception {log.error(请求发生异常URL{}HTTP_METHOD{}IP{}错误信息{}, req.getRequestURL().toString(),req.getMethod(), req.getRemoteAddr(), e.getMessage());ResultModel resultModel;//异常结果处理步骤return resultModel;}
}注解简介
RestControllerAdvice是一个组合注解由ControllerAdvice、ResponseBody组成而ControllerAdvice继承了Component因此RestControllerAdvice本质上是个Component用于定义ExceptionHandlerInitBinder和ModelAttribute方法适用于所有使用RequestMapping方法。 RestControllerAdvice注解将作用在所有注解了RequestMapping的控制器的方法上该注解有一些属性可以设定具体的范围。 Tips上文提到的一些注解的基础用法网上资料很多这边不展开。 实战分析
接下来谈谈博主所在企业是如何实现这一异常处理器的它到底可以做或者应该做哪些事情 Step1、从上下文获取链路ID设置到响应头并设置响应状态代码如下。
ExceptionHandler(Exception.class)
public Object exceptionHandler(Exception ex) {IResult? result;try {String traceId OnelinkContextHolder.getString(OnelinkConstant.TRACE_ID);// 响应头增加链路IDthis.response.setHeader(OnelinkConstant.TRACE_ID, StrUtil.nullToEmpty(traceId));// 先默认设置HTTP状态码为500,然后根据具体异常处理再调整对应的状态码this.response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);// 统一分发并处理异常result this.handleException(ex);} catch (Exception e) {log.error(全局异常处理发生错误, e);result ResultVO.failure(ex.getMessage(), ExceptionUtil.stacktraceToString(e));}// 是否开启异常处理指南if (!this.onelinkExceptionGuideProviders.isEmpty()) {this.appendExGuide(ex, result);}return result;
}Step2、针对框架自定义的异常拦截器接口进行遍历先执行前置接口再执行后置接口这个思想贯穿整个框架搭建过程预留给各小组的业务开发人员更多扩展空间那什么遵循开闭原则对修改关闭对扩展开放。
// 异常拦截器
if (this.interceptors ! null) {for (WebExceptionInterceptor interceptor : this.interceptors) {ex interceptor.beforeHandle(ex);}
}public interface WebExceptionInterceptor {/*** 全局异常处理前逻辑*/default Exception beforeHandle(Exception ex) {return ex;}/*** 全局异常处理后逻辑*/default Exception afterHandle(Exception ex, ResultVOObject resultVO) {return ex;}}3、最后就是本职工作了针对异常的不同类型进行不同的组装比如ORA-开头的异常做出翻译处理等还有一些异常日志记录、是否异常指引等功能这里不展开了。 ResponseBodyAdvice 实现返回值包装
技术说明
0、ResponseBodyAdvice 是 Spring Framework 的 Web 模块中的一个接口它允许你在将响应体写入 HTTP 响应之前拦截和修改它。它提供了一种全局定制响应处理逻辑的方式适用于 Spring MVC 或 Spring WebFlux 应用程序。 1、ResponseBodyAdvice 可以在注解 ResponseBody 将返回值处理成相应格式之前操作返回值实现这个接口即可完成相应操作可用于对response 数据的一些统一封装或者加密等操作。 2、ResponseBodyAdvice 接口和 RequestBodyAdvice 接口类似RequestBodyAdvice 是请求到Controller 之前拦截做相应的处理操作而ResponseBodyAdvice 是对Controller返回的{code ResponseBody}or a {code ResponseEntity} 后{code HttpMessageConverter} 类型转换之前拦截进行相应的处理操作后再将结果返回给客户端。 3、实现 ResponseBodyAdvice 接口需要重写其 supports 和 beforeBodyWrite 方法。 1supports方法判断是否要执行beforeBodyWrite方法true为执行false不执行。通过该方法可以选择哪些类或那些方法的response要进行处理其他的不进行处理。 2beforeBodyWrite方法对response方法进行具体操作处理。
public interface ResponseBodyAdviceT {/*** 1、选择是否执行 beforeBodyWrite 方法返回 true 执行false 不执行* 2、通过 supports 方法可以选择对哪些类或方法的 Response 进行处理* param returnType返回类型* param converterType转换器* return 返回 true 则下面的 beforeBodyWrite 执行否则不执行*/boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType);/*** 对 Response 处理的具体执行方法* param body响应对象(response)中的响应体* param returnType控制器方法的返回类型* param selectedContentType通过内容协商选择的内容类型* param selectedConverterType选择写入响应的转换器类型* param request当前请求* param response当前响应* return 返回传入的主体或修改过的(可能是新的)主体*/NullableT beforeBodyWrite(Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdviceObject {Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 根据返回类型和转换器类型检查是否应用此建议// 你可以在这里放置任何条件return true;}Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType,Class selectedConverterType, ServerHttpRequest request,ServerHttpResponse response) {// 在将响应体写入输出流之前修改它// 你可以在这里检查或修改 body 对象return body;}
}总结ResponseBodyAdvice 接口允许在执行 ResponseBody 或 ResponseEntity 控制器方法之后但在使用 HttpMessageConverter 写入响应体之前自定义响应进行功能增强。通常用于加密签名统一数据格式等。 注意要使其生效参考框架代码关键点是RestControllerAdvice。 实战分析
可以用于针对返回数据进行处理要特别注意如下点
异常结果的处理Feign调用结果的处理普通数据的处理其他数据的处理
核心思路就是设置一个返回值类根据返回数据的类型是否为该类进行判断处理。
public Object beforeBodyWrite(Object responseBody,NonNull MethodParameter methodParameter,NonNull MediaType mediaType,NonNull Class? extends HttpMessageConverter? clazz,NonNull ServerHttpRequest serverHttpRequest,NonNull ServerHttpResponse serverHttpResponse) {HttpHeaders reqHeaders serverHttpRequest.getHeaders();String disableWrapperFlag reqHeaders.getFirst(ResultWrapper.DISABLE_WRAPPER_HEADER_KEY);String rpcClient reqHeaders.getFirst(RpcConstant.RPC_CLIENT_HEADER_NAME);if (this.couldSkip(mediaType, disableWrapperFlag, rpcClient)) {return responseBody;}Type type methodParameter.getExecutable().getAnnotatedReturnType().getType();String traceId this.traceIdProvider null ? null : this.traceIdProvider.getTraceId();Object result;// 远程调用直接返回if (responseBody instanceof ApiResult?) {result responseBody;// 为返回结果设置链路ID} else if (responseBody instanceof IResult) {ResultVO? resultVO (ResultVO?) responseBody;result StrUtil.isBlank(resultVO.getTraceId()) ? resultVO.setTraceId(traceId) : resultVO;this.setResultEnv(resultVO);// 如果返回结果是字符串,不能直接返回ResultVO,否则会与StringHttpMessageConverter冲突} else if (responseBody instanceof String || type String.class) {ResultVO? resultVO ResultVO.success(responseBody).setTraceId(traceId);result JSON.toJSONString(resultVO, SerializerFeature.WriteMapNullValue);serverHttpResponse.getHeaders().add(content-type, ContentType.JSON.toString());// 没有被IResult包装,默认使用ResultVO进行包装} else {ResultVOObject resultVO ResultVO.success(responseBody).setTraceId(traceId);this.setResultEnv(resultVO);result resultVO;}return result;
}还可以用于链路追踪返回数据Span的数据二次处理比如返回值长度截取等具体不展开了。
String responseTempStr JSONObject.toJSONString(responseBody);
String truncatedResult responseTempStr.length() 2000 ? responseTempStr.substring(0, 2000) ... : responseTempStr;
span.tag(TraceSpanConstant.HTTP_RESPONSE, truncatedResult);其他方式
如果您的项目需要针对返回值做了一些自定义扩展或处理除了可以使用ResponseBodyAdvice还可以考虑一下下面两个关键词MessageConverters、 HandlerMethodReturnValueHandler这里篇幅受限就不展开了。 总结陈词
上文介绍了框架封装人员针对框架的统一异常和返回值包装的处理过程仅供参考。 本系列博文后续继续更新介绍框架搭建人员如何以恰当的方式应对各式各样的情况这也是此专栏的主题。 后续将持续更新请多多支持