《框架封装 · 统一异常处理和返回值包装》
📢 大家好,我是 【战神刘玉栋】,有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);// 响应头增加链路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, 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 :返回传入的主体或修改过的(可能是新的)主体*/@NullableT beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {@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 {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,还可以考虑一下下面两个关键词:MessageConverters、 HandlerMethodReturnValueHandler,这里篇幅受限就不展开了。
总结陈词
上文介绍了框架封装人员,针对框架的统一异常和返回值包装的处理过程,仅供参考。
 本系列博文后续继续更新,介绍框架搭建人员如何以恰当的方式应对各式各样的情况,这也是此专栏的主题。
 后续将持续更新,请多多支持!
相关文章:
《框架封装 · 统一异常处理和返回值包装》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
深入WebKit:揭秘复杂文档的高效渲染之道
深入WebKit:揭秘复杂文档的高效渲染之道 在当今信息爆炸的时代,网页不再仅仅是简单的文本和图片的集合,而是充满了复杂布局和丰富媒体内容的交互式平台。WebKit 作为众多流行浏览器的心脏,其布局引擎承担着将 HTML、CSS 代码转换…...
 
进程的控制-孤儿进程和僵尸进程
孤儿进程 : 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程( 进程号为 1) 所收养,并由 init 进程对它们完成状态收集工作 为了释放子进程的占用的系统资源: …...
 
【Unity navigation面板】
【Unity navigation面板】 Unity的Navigation面板是一个集成在Unity编辑器中的界面,它允许开发者对导航网格(NavMesh)进行配置和管理。 Unity Navigation面板的一些关键特性和功能: 导航网格代理(NavMesh Agent&…...
二刷算法训练营Day53 | 动态规划(14/17)
目录 详细布置: 1. 392. 判断子序列 2. 115. 不同的子序列 详细布置: 1. 392. 判断子序列 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余…...
将缓冲文件写到磁盘中的命令sync
将缓冲文件写到磁盘中的命令sync There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave quick…...
 
灵活视图变换器:为扩散模型设计的革新图像生成架构
在自然界中,图像的分辨率是无限的,而现有的图像生成模型在跨任意分辨率泛化方面存在困难。虽然扩散变换器(DiT)在特定分辨率范围内表现出色,但在处理不同分辨率的图像时却力不从心。为了克服这一限制,来自上…...
[终端安全]-1 总体介绍
有朋友一直在和笔者研讨智驾安全这个热门话题,笔者十多年工作从不离终端安全这个核心话题(芯片安全、操作系统安全、应用安全),近来也一直在梳理终端安全体系;手机、汽车皆是我们生活中应用最普遍的智能终端࿰…...
 
Mysql5.7并发插入死锁问题
死锁的产生条件 互斥、请求和保持、不可剥夺、循环等待 MySQL锁类型 死锁复现 环境:Mysql 5.7版本,Innodb引擎,可重复度隔离级别 并发场景下使用duplicate key update插入或更新数据可能会造成死锁,下面就产生死锁的条件进行模…...
网络“ping不通”,如何排查和解决呢?
网络问题往往复杂且难以预测,其中“ping不通”是常见的网络故障之一。 1. 确认问题现象 首先,明确问题是完全无法ping通(无响应)还是ping通但有高延迟或丢包。这有助于缩小问题范围。 2. 本地检查 网络接口状态:使用ifconfig(Linux)或ipc…...
日常学习--20240706
1、udp协议的特点有哪些? a、无连接,发送和接收数据不需要建立连接,开销小,实时性好 b、不可靠传输,不保证数据包能够到达目的地,也不保证数据包的顺序 c、面向数据报的,以数据报形式发送数据…...
 
入门PHP就来我这(高级)12 ~ 获取数据
有胆量你就来跟着路老师卷起来! -- 纯干货,技术知识分享 路老师给大家分享PHP语言的知识了,旨在想让大家入门PHP,并深入了解PHP语言。 1 从结果集中获取一行作为对象 表中数据行如下: 利用mysqli_fetch_array()函数获…...
 
AIGC专栏12——EasyAnimateV3发布详解 支持图文生视频 最大支持960x960x144帧视频生成
AIGC专栏12——EasyAnimateV3发布详解 支持图&文生视频 最大支持960x960x144帧视频生成 学习前言项目特点生成效果相关地址汇总项目主页Huggingface体验地址Modelscope体验地址源码下载地址 EasyAnimate V3详解技术储备Diffusion Transformer (DiT)Hybrid Motion ModuleU-V…...
 
【python】python猫眼电影数据抓取分析可视化(源码+数据集+论文)【独一无二】
👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…...
 
Android 四大组件
1. Activity 应用程序中,一个Activity通常是一个单独的屏幕,它上面可以显示一些控件,也可以监听并对用户的事件做出响应。 Activity之间通过Intent进行通信,在Intent 的描述结构中,有两个最重要的部分:动…...
 
【Python】已解决:ModuleNotFoundError: No module named ‘nltk’
文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决:ModuleNotFoundError: No module named ‘nltk’ 一、分析问题背景 在使用Python进行自然语言处理或文本分析时,我们经常会用到各种库来辅助我们的工…...
 
【Docker系列】Docker 命令行输出格式化指南
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
使用Netty构建高性能的网络应用
使用Netty构建高性能的网络应用 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! Netty是一个基于Java NIO的异步事件驱动的网络应用框架,专为快速开发高性能、高可靠性的网络服务器和客户…...
 
C++11新特性【下】{lambda表达式、可变模板参数、包装器}
一、lambda表达式 在C98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。如果待排序元素为自定义类型,需要用户定义排序时的比较规则,随着C语法的发展,人们开始觉得上面的写法太复杂了,…...
SpringBoot使用手册
SpringBoot使用手册 1、自动装配 1.1、创建spring Boot项目 在之前的文章中已经专门写过,这里不做赘述。 1.2、pom.xml 1.2.1、版本管理 在学习完maven项目后,我们学习框架时首先阅读的就是pom.xml文件,这里是管理自己该项目中所用到的…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
 
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
 
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
 
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
 
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
 
自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
