《框架封装 · 统一异常处理和返回值包装》

news2024/10/6 1:52:51

📢 大家好,我是 【战神刘玉栋】,有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,用于定义@ExceptionHandler,@InitBinder和@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);
        // 响应头增加链路ID
        this.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, ResultVO<Object> 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 方法。
1)supports方法:判断是否要执行beforeBodyWrite方法,true为执行,false不执行。通过该方法可以选择哪些类或那些方法的response要进行处理,其他的不进行处理。
2)beforeBodyWrite方法:对response方法进行具体操作处理。

public interface ResponseBodyAdvice<T> {
    /**
     * 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 :返回传入的主体或修改过的(可能是新的)主体
     */
	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 根据返回类型和转换器类型检查是否应用此建议
        // 你可以在这里放置任何条件
        return true;
    }

    @Override
    public 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 {
        ResultVO<Object> 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,还可以考虑一下下面两个关键词:MessageConvertersHandlerMethodReturnValueHandler,这里篇幅受限就不展开了。


总结陈词

上文介绍了框架封装人员,针对框架的统一异常和返回值包装的处理过程,仅供参考。
本系列博文后续继续更新,介绍框架搭建人员如何以恰当的方式应对各式各样的情况,这也是此专栏的主题。
后续将持续更新,请多多支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1903765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【全面介绍下如何使用Zoom视频会议软件!】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Halcon 背景网格产品刮伤缺陷检测

* 关闭窗口 dev_close_window ()*关闭程序计数器,图形变量更新,窗口图形更新 dev_update_off ()*设置图像路径 Path : lcd/mura_defects_blur_*读取一张图像 read_image (Image, Path 01)*获取图像大小 get_image_size (Image, Width, Height)*创建一个新窗体 dev_open_window…

昇思25天学习打卡营第13天 | LLM原理和实践:文本解码原理--以MindNLP为例

1. 文本解码原理--以MindNLP为例 1.1 自回归语言模型 根据前文预测下一个单词 一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积 W 0 W_0 W0​:初始上下文单词序列 t t t: 时间步 当生成EOS标签时&#xff0c;停止生成。 MindNLP/huggingface Transfor…

NewStarCTF2023-Misc

目录 week1 CyberChefs Secret 机密图片 流量&#xff01;鲨鱼&#xff01; 压缩包们 空白格 隐秘的眼睛 week2 新建Word文档 永不消逝的电波 1-序章 base! WebShell的利用 Jvav week3 阳光开朗大男孩 大怨种 2-分析 键盘侠 滴滴滴 week4 通大残 Nmap 依…

Unity AssetsBundle 详解

文章目录 1.AssetBundle 概念2.AssetBundle 优势3.AssetBundle 特性4.AssetBundle 使用流程4.1 分组4.2 打包4.3 加载包4.4 加载资源4.5 卸载资源 5.AssetBundleManifest6.AssetBundle的内存占用7.AB包资源加密 1.AssetBundle 概念 AssetBundle又称AB包&#xff0c;是Unity提供…

Python视觉轨迹几何惯性单元超维计算结构算法

&#x1f3af;要点 &#x1f3af;视觉轨迹几何惯性单元超维计算结构算法 | &#x1f3af;超维计算结构视觉场景理解 | &#x1f3af;超维计算结构算法解瑞文矩阵 | &#x1f3af;超维矢量计算递归神经算法 &#x1f36a;语言内容分比 &#x1f347;Python蒙特卡罗惯性导航 蒙…

【漏洞复现】宏景eHR LoadOtherTreeServlet SQL注入漏洞

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合&#xff0c;满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR LoadOtherTreeServlet接口处存在SQL注入漏洞&#xff0c;未经身份验证的远程攻击者除了可以利用 SQL 注入漏…

[Multi-Modal] MDETR 论文及代码学习笔记

代码地址&#xff1a;https://github.com/ashkamath/mdetr 论文地址&#xff1a;https://arxiv.org/abs/2104.12763 多模态推理系统依靠预先训练的目标检测器从图像中提取感兴趣区域&#xff08;边界框包围区域&#xff09;。然而&#xff0c;这个关键模块通常被用作黑匣子&…

MySQL高级----详细介绍MySQL中的锁

概述 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff0c;为了解决数据访问的一致性和有效性问题。在数据库中&#xff0c;除传统的计算资源(CPU、RAN、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、…

windows无法访问github

##一、如果发现windows无法访问github时 一般就是我们的dns出现了问题&#xff0c;此时我们需要更换一个dns访问 ##二、解决方法 首先我们访问ip查询地址&#xff0c; https://ipchaxun.com/github.com/ 可更换下面历史ip进行测试&#xff0c;在windows的cmd里面输入ping git…

【C++深度探索】:继承(定义赋值兼容转换作用域派生类的默认成员函数)

✨ 愿随夫子天坛上&#xff0c;闲与仙人扫落花 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞…

pin是什么?管脚

1.平面分割 1)启动Allegro PCB design &#xff0c;打开.brd。深色部分属于一个net&#xff0c;要做一下修改&#xff0c;将上面的pin包含进shape中&#xff0c;i进行a&#xff0c;b两步操作&#xff0c;删除以前存在的Anti Etch下的line&#xff0c;再将其进行补齐 使它保住上…

MSPM0G3507——OPENMV给M0传数据(用数据包)互相通信(以循迹为例)

OPENMV端代码 # main.py -- put your code here! import pyb, sensor, image, math, time from pyb import UART import ustruct from image import SEARCH_DS, SEARCH_EX import time import sensor, displayuart UART(3, 115200, bits8, parityNone, stop1, timeout_char10…

Pogo-DroneCANPWM模块:可实现DroneCAN转PWM,DroneCAN转dshot,DroneCAN转bdshot

关键词&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;PWM&#xff0c;dshot&#xff0c;bdshot&#xff0c;DroneCANPWM&#xff0c;电调ESC&#xff0c;DroneCAN&#xff0c;UAVCAN&#xff0c;飞控&#xff0c;无人机&#xff0c;UAV Keywords&#xff1a;Ardupilot…

Xilinx FPGA:vivado串口输入输出控制fifo中的数据

一、实验要求 实现同步FIFO回环测试&#xff0c;通过串口产生数据&#xff0c;写入到FIFO内部&#xff0c;当检测到按键信号到来&#xff0c;将FIFO里面的数据依次读出。 二、信号流向图 三、状态转换图 四、程序设计 &#xff08;1&#xff09;按键消抖模块 timescale 1ns…

Python编程学习笔记(1)--- 变量和简单数据类型

1、变量 在学习编程语言之前&#xff0c;所接触的第一个程序&#xff0c;绝大多数都是&#xff1a; print("Hello world!") 接下来尝试使用一个变量。在代码中的开头添加一行代码&#xff0c;并对第二行代码进行修改&#xff0c;如下&#xff1a; message "…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

什么时候考虑将mysql数据迁移到ES?

文章目录 对ES的一些疑问问题1:ES相比mysql本身有哪些优势&#xff1f;问题2:哪些场景适合用ES而不是mysql&#xff1f;问题3:mysql逐行扫描&#xff0c;根据过滤条件检查记录中对应字段是否满足要求属于正排索引&#xff0c;根据二叉树索引检索记录的方式属于正排索引还是倒排…

LeetCode 189.轮转数组 三段逆置 C写法

LeetCode 189.轮转数组 C写法 三段逆置 思路: 三段逆置方法:先逆置前n-k个 再逆置后k个 最后整体逆置 由示例1得&#xff0c;需要先逆置1,2,3,4 再逆置5,6,7&#xff0c;最后前n-k个与后k个逆置 代码 void reverse(int*num, int left, int right) //逆置函数 { while(left …

XLSX + LuckySheet + LuckyExcel + Web Worker实现前端的excel预览

文章目录 功能简介简单代码实现web worker 版本效果参考 功能简介 通过LuckyExcel的transformExcelToLucky方法&#xff0c; 我们可以把一个文件直接转成LuckySheet需要的json字符串&#xff0c; 之后我们就可以用LuckySheet预览excelLuckyExcel只能解析xlsx格式的excel文件&a…