装饰SpringMVC的适配器实现响应自动包装
文章目录
- 1.common-tool-starter
- 1.目录结构
- 2.ResultWrapper.java
- 2.common-web-starter
- 1.目录结构
- 2.IgnoredResultWrapper.java 自定义注解,忽略对返回结果的自动包装
- 3.ReturnValueHandlersDecorator.java 对适配器进行扩展的装饰器
- 4.WebAutoConfiguration.java 将装饰器注入容器
- 3.common-web-starter-demo
- 1.WebController.java 测试三种使用方式
- 2.测试
- 1.第一种:直接使用自动包装成功结果
- 2.第二种:使用 @IgnoredResultWrapper注解忽略掉自动包装
- 3.第三种:直接使用ResultWrapper来自己封装结果
1.common-tool-starter
1.目录结构

2.ResultWrapper.java
package com.sunxiansheng.tool.response;import lombok.Data;import java.io.Serializable;/*** Description: 通用响应封装类,通过枚举来获取code和message** @Author sun* @Create 2024/10/11* @Version 1.0*/
@Data
public class ResultWrapper<T> implements Serializable {private static final long serialVersionUID = 1L;// 是否成功private boolean success;// 响应代码private int code;// 响应消息private String message;// 响应数据private T data;// 私有构造器,防止外部直接创建private ResultWrapper() {}// 使用内部建造者类进行对象构建public static class Builder<T> {private boolean success;private int code;private String message;private T data;// ============================== 链式调用设置建造者对象 ==============================public Builder<T> success(boolean success) {this.success = success;return this;}public Builder<T> code(int code) {this.code = code;return this;}public Builder<T> message(String message) {this.message = message;return this;}public Builder<T> data(T data) {this.data = data;return this;}// ============================== 链式调用设置建造者对象 ==============================// ============================== 构建ResultWrapper对象 ==============================public ResultWrapper<T> build() {ResultWrapper<T> result = new ResultWrapper<>();result.success = this.success;result.code = this.code;result.message = this.message;result.data = this.data;return result;}// ============================== 构建ResultWrapper对象 ==============================}// ============================== 快捷方法 ==============================public static <T> ResultWrapper<T> ok() {return new Builder<T>().success(true).code(RespBeanEnum.SUCCESS.getCode()).message(RespBeanEnum.SUCCESS.getMessage()).build();}public static <T> ResultWrapper<T> ok(T data) {return new Builder<T>().success(true).code(RespBeanEnum.SUCCESS.getCode()).message(RespBeanEnum.SUCCESS.getMessage()).data(data).build();}public static <T> ResultWrapper<T> ok(T data, String message) {return new Builder<T>().success(true).code(RespBeanEnum.SUCCESS.getCode()).message(message).data(data).build();}public static <T> ResultWrapper<T> fail() {return new Builder<T>().success(false).code(RespBeanEnum.ERROR.getCode()).message(RespBeanEnum.ERROR.getMessage()).build();}public static <T> ResultWrapper<T> fail(String message) {return new Builder<T>().success(false).code(RespBeanEnum.ERROR.getCode()).message(message).build();}public static <T> ResultWrapper<T> fail(int code, String message) {return new Builder<T>().success(false).code(code).message(message).build();}public static <T> ResultWrapper<T> fail(int code, String message, T data) {return new Builder<T>().success(false).code(code).message(message).data(data).build();}public static <T> ResultWrapper<T> fail(RespBeanEnum respBeanEnum) {return new Builder<T>().success(false).code(respBeanEnum.getCode()).message(respBeanEnum.getMessage()).build();}// ============================== 快捷方法 ==============================
}
2.common-web-starter
1.目录结构

2.IgnoredResultWrapper.java 自定义注解,忽略对返回结果的自动包装
package com.sunxiansheng.web.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Description: 忽略对返回结果的自动包装** @Author sun* @Create 2025/1/6 15:58* @Version 1.0*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoredResultWrapper {}
3.ReturnValueHandlersDecorator.java 对适配器进行扩展的装饰器
package com.sunxiansheng.web.config;import com.sunxiansheng.tool.response.ResultWrapper;
import com.sunxiansheng.web.annotation.IgnoredResultWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** Description: 装饰器** @Author sun* @Create 2025/1/6 14:40* @Version 1.0*/
@Slf4j
public class ReturnValueHandlersDecorator implements InitializingBean {/*** 注入适配器,可以获取*/@Resourceprivate RequestMappingHandlerAdapter requestMappingHandlerAdapter;@Overridepublic void afterPropertiesSet() {List<HandlerMethodReturnValueHandler> returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();// 因为默认List<HandlerMethodReturnValueHandler>是不可修改列表,所以需要给他转换成一个可修改的列表assert returnValueHandlers != null;List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);// 装饰一下然后重新设置this.decorateHandlers(handlers);// 将装饰后的handlers替换原来的不可变列表requestMappingHandlerAdapter.setReturnValueHandlers(handlers);}private void decorateHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {if (returnValueHandler instanceof RequestResponseBodyMethodProcessor) {// 找到RequestResponseBodyMethodProcessorControllerReturnValueHandler controllerReturnValueHandler = new ControllerReturnValueHandler(returnValueHandler);// 找到在原列表中的索引位置int index = returnValueHandlers.indexOf(returnValueHandler);// 将装饰后的HandlerMethodReturnValueHandler放到原来的位置returnValueHandlers.set(index, controllerReturnValueHandler);}}}private static class ControllerReturnValueHandler implements HandlerMethodReturnValueHandler {// 拿到被装饰的对象private final HandlerMethodReturnValueHandler handler;public ControllerReturnValueHandler(HandlerMethodReturnValueHandler handler) {this.handler = handler;}/*** 保持跟原生的一致** @param returnType* @return*/@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);}@Overridepublic void handleReturnValue(Object o, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws Exception {IgnoredResultWrapper methodAnnotation = methodParameter.getMethodAnnotation(IgnoredResultWrapper.class);// 如果有IgnoredResultWrapper注解,就不进行包装,走原来的逻辑if (Objects.nonNull(methodAnnotation)) {handler.handleReturnValue(o, methodParameter, modelAndViewContainer, nativeWebRequest);return;}// 如果返回值是ResultWrapper类型,也不进行包装,走原来的逻辑if (o instanceof ResultWrapper) {handler.handleReturnValue(o, methodParameter, modelAndViewContainer, nativeWebRequest);return;}// 其余情况,进行包装log.info("Controller返回值已被自动包装,如果上传文件,请加@IgnoredResultWrapper注解取消自动包装!");ResultWrapper<Object> ok = ResultWrapper.ok(o);handler.handleReturnValue(ok, methodParameter, modelAndViewContainer, nativeWebRequest);}}
}
4.WebAutoConfiguration.java 将装饰器注入容器
/*** 注入对返回结果增强的装饰器** @return*/@Bean@ConditionalOnMissingBeanpublic ReturnValueHandlersDecorator returnValueHandlersDecorator() {return new ReturnValueHandlersDecorator();}
3.common-web-starter-demo
1.WebController.java 测试三种使用方式
package com.sunxiansheng.web.controller;import com.sunxiansheng.tool.response.ResultWrapper;
import com.sunxiansheng.web.annotation.IgnoredResultWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** Description: 测试** @Author sun* @Create 2024/10/4 22:56* @Version 1.0*/
@RestController
public class WebController {/*** 第一种方式:直接使用自动包装成功结果** @return*/@RequestMapping("/method1")public String method1() {return "method1";}/*** 第二种方式:使用 @IgnoredResultWrapper注解忽略掉自动包装** @return*/@IgnoredResultWrapper@RequestMapping("/method2")public String method2() {return "method2";}/*** 第三种方式:直接使用ResultWrapper来自己封装结果** @return*/@RequestMapping("/method3")public ResultWrapper<String> method3() {return ResultWrapper.fail("method3");}
}
2.测试
1.第一种:直接使用自动包装成功结果

2.第二种:使用 @IgnoredResultWrapper注解忽略掉自动包装

3.第三种:直接使用ResultWrapper来自己封装结果

相关文章:
装饰SpringMVC的适配器实现响应自动包装
文章目录 1.common-tool-starter1.目录结构2.ResultWrapper.java 2.common-web-starter1.目录结构2.IgnoredResultWrapper.java 自定义注解,忽略对返回结果的自动包装3.ReturnValueHandlersDecorator.java 对适配器进行扩展的装饰器4.WebAutoConfiguration.java 将装…...
【Rust自学】15.4. Drop trait:告别手动清理,释放即安全
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 15.4.1. Drop trait的意义 类型如果实现了Drop trait,就可以让程序员自定义当值…...
【算法】【归并排序】AcWing 算法基础 788. 逆序对的数量
题目 给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。 逆序对的定义如下:对于数列的第 i个和第 j 个元素,如果满足 i<j且 a[i]>a[j],则其为一个逆序对;否则不是。 输入格式 第一行包含整数 n&#…...
一个局域网通过NAT访问另一个地址重叠的局域网(IP方式访问)
正文共:1335 字 7 图,预估阅读时间:4 分钟 现在,我们已经可以通过调整两台设备的组合配置(地址重叠时,用户如何通过NAT访问对端IP网络?)或仅调整一台设备的配置(仅操作一…...
05-机器学习-数据标注
一、学习数据标注的核心目标 数据标注不仅是“打标签”,而是理解数据与AI模型之间的桥梁。需要掌握: 标注技术:不同任务类型的标注方法(如分割、实体识别)。标注工具:高效使用专业工具(如CVAT…...
LQ1052 Fibonacci斐波那契数列
题目描述 Fibonacci斐波那契数列也称为兔子数列,它的递推公式为:FnFn-1Fn-2,其中F1F21。 当n比较大时,Fn也非常大,现在小蓝想知道,Fn除以10007的余数是多少,请你编程告诉她。 输入 输入包含一…...
AWTK 骨骼动画控件发布
Spine 是一款广泛使用的 2D 骨骼动画工具,专为游戏开发和动态图形设计设计。它通过基于骨骼的动画系统,帮助开发者创建流畅、高效的角色动画。本项目是基于 Spine 实现的 AWTK 骨骼动画控件。 代码:https://gitee.com/zlgopen/awtk-widget-s…...
分库分表后如何进行join操作
在分库分表后的系统中,进行表之间的 JOIN 操作比在单一数据库表中复杂得多,因为涉及的数据可能位于不同的物理节点或分片中。此时,传统的 SQL JOIN 语句不能直接用于不同分片的数据,以下是几种处理这样的跨分片 JOIN 操作的方法&a…...
arkui-x 前端布局编码模板
build() {Column() {Row() {// 上侧页面布局实现}// 下侧页面布局实现}.width(Const.THOUSANDTH_1000).height(Const.THOUSANDTH_1000).justifyContent(FlexAlign.SpaceBetween).backgroundImage($r(app.media.background_xxx)).backgroundImageSize(ImageSize.Cover).backgrou…...
宝塔面板SSL加密访问设置教程
参考:https://www.bt.cn/bbs/thread-117246-1-1.html 如何快速使用证书加密访问面板 因早期默认未开启https访问所以没有相关的风险提醒,现面板默认已开启https加密访问、提升安全性 由于采用的是服务器内部本身签发证书,不被公网浏览器信任请参考以下步…...
c++ set/multiset 容器
1. set 基本概念 简介: 所有元素都会在插入时自动排序本质: set/multiset属于关联式容器,底层结构是用二叉树实现。set 和 multiset 区别: set容器不允许有重复的元素。 multiset允许有重复的元素。2. set 构造和赋值 构造&a…...
前部分知识复习02
一、物体的屏幕UV坐标 float2 ScreenUV i.pos.xy / _ScreenParams.xy; 二、抓取屏幕图像 GrabPass{" _A "} //_A为贴图图像名称 之后需在Pass中声明该贴图才能在Pass中引用此贴图 三、屏幕抓取并制作热效应代码 Shader"unity/HeatDistort 07" {Pr…...
开发环境搭建-3:配置 JavaScript 开发环境 (fnm+ nodejs + pnpm + nrm)
在 WSL 环境中配置:WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 node 官网:https://nodejs.org/zh-cn/download 点击【下载】,选择想要的 node 版本、操作系统、node 版本管理器、npm包管理器 根据下面代码提示依次执行对应代码即可 基本概…...
kotlin内联函数——let,run,apply,also,with的区别
一、概述 为了帮助您根据使用场景选择合适的作用域函数(scope function),我们将对它们进行详细描述并提供使用建议。从技术上讲,许多情况下范围函数是可以互换使用的,因此示例中展示了使用它们的约定俗成的做法。 1.…...
【深度学习|DenseNet-121】Densely Connected Convolutional Networks内部结构和参数设置
【深度学习|DenseNet-121】Densely Connected Convolutional Networks内部结构和参数设置 【深度学习|DenseNet-121】Densely Connected Convolutional Networks内部结构和参数设置 文章目录 【深度学习|DenseNet-121】Densely Connected Convolutional Networks内部结构和参数…...
数据结构与算法-要点整理
知识导图: 一、数据结构 包含:线性表(数组、队列、链表、栈)、散列表、树(二叉树、多路查找树)、图 1.线性表 数据之间就是“一对一“的逻辑关系。 线性表存储数据的实现方案有两种,分别是顺序存储结构和链式存储结构。 包含:数组、队列、链表、栈。 1.1 数组…...
Fort Firewall:全方位守护网络安全
Fort Firewall是一款专为 Windows 操作系统设计的开源防火墙工具,旨在为用户提供全面的网络安全保护。它基于 Windows 过滤平台(WFP),能够与系统无缝集成,确保高效的网络流量管理和安全防护。该软件支持实时监控网络流…...
Nginx实战技巧(Practical Tips for nginx)
引言 简介 Nginx(发音为 "engine-x")是一个高性能的HTTP和反向代理服务器. Nginx以其高并发处理能力、低资源消耗和灵活的配置而闻名,适用于高流量的Web服务器和应用程序。 Nginx的主要功能包括: HTTP服务器…...
YOLOv8:目标检测与实时应用的前沿探索
随着深度学习和计算机视觉技术的迅速发展,目标检测(Object Detection)一直是研究热点。YOLO(You Only Look Once)系列模型作为业界广受关注的目标检测框架,凭借其高效、实时的特点,一直迭代更新…...
解锁数字经济新动能:探寻 Web3 核心价值
随着科技的快速发展,我们正迈入一个全新的数字时代,Web3作为这一时代的核心构成之一,正在为全球数字经济带来革命性的变革。本文将探讨Web3的核心价值,并如何推动数字经济的新动能。 Web3是什么? Web3,通常…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
