SpringBoot ResponseBodyAdvice使用以及常见问题

简介

PS:

  1. advice, 在这里意思是顾问, 其余很多场景也是顾问的意思
  2. 由于篇幅问题, 注释已删, 如想看注释, 请在github中查看

作用: 用于在Controller返回后, HttpMessageConverter执行转换之前执行一些转换

常见场景: 统一响应结构, 如json统一包装

由于版本不同, 多少有些差异, 所以不贴源码了, 基本上springboot2.x和3.x是通用的

简单做个翻译(springboot3.1.5为例):

public interface ResponseBodyAdvice<T> {/*** 此Advice是否使用于该返回类型和Converter类型(意思是可以配置多个哦)* @param returnType 返回类型(这里可以获取很多东西, 别被名字误导了)* @param converterType 自动选择的转换器类型* @return 返回true表示将会走接下来的方法(beforeBodyWrite), 否则不会*/boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);/*** HttpMessageConverter转换之前进行的操作* @param body 要转换的body* @param returnType 返回类型* @param selectedContentType 根据请求头协商的ContentType* @param selectedConverterType 自动选择的转换器类型* @param request 当前请求* @param response 当前响应* @return 修改后的响应内容*/@NullableT beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response);}

示例

@RestControllerAdvice
public class ResAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {return returnType.getContainingClass().getPackageName().startsWith("kim.nzxy.ly");}@Overridepublic Object beforeBodyWrite(Object body,@NotNull MethodParameter returnType,@NotNull MediaType selectedContentType,@NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,@NotNull ServerHttpRequest request,@NotNull ServerHttpResponse response) {if (body instanceof Res<?> || !selectedContentType.equals(MediaType.APPLICATION_JSON)) {return body;}if (body instanceof Page<?>) {// 我的分页有特殊处理return Res.page((Page<?>)body);}return Res.ok(body);}
}

解释一下代码:

supports判断, 如果类为自己的包下的类, 则允许处理

beforeBodyWrite作用:

如果响应内容不是JSON(可能是文件之类的), 或者已经被公共响应(Res)类包装过了, 就直接返回;

否则则在外面包装一层Res类

附Res.java

package kim.nzxy.ly.common.res;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import kim.nzxy.ly.common.exception.LyException;
import lombok.Data;
import lombok.experimental.Accessors;@Data
public class Res<T> {private static final String SUCCESS_MESSAGE = "操作成功";private String message;private int code;private T data;private long timestamp = System.currentTimeMillis();public static <T> Res<T> ok(T data, String message) {if (data instanceof Page) {throw new LyException.Panic("使用Res#page方法来返回分页数据");}Res<T> msg = new Res<T>();msg.setCode(2000);msg.setMessage(message);msg.setData(data);return msg;}public static <T> Res<T> ok(T data) {return Res.ok(data, SUCCESS_MESSAGE);}public static <T> Res<T> ok(String message) {// noinspection uncheckedreturn Res.ok((T) message, message);}public static <T> Res<T> ok() {return Res.ok(null, SUCCESS_MESSAGE);}public static <T> Res<T> fail(String message, int code) {Res<T> msg = new Res<>();msg.setCode(code);msg.setMessage(message);return msg;}public static <T> Res<T> fail(String message) {return Res.fail(message, 5000);}public static <T> Res<PagingVO<T>> page(Page<T> page) {PagingVO<T> data = new PagingVO<>();data.setPages(Math.toIntExact(page.getPages()));data.setPageSize(Math.toIntExact(page.getSize()));data.setList(page.getRecords());data.setTotal(Math.toIntExact(page.getTotal()));data.setPageNum(Math.toIntExact(page.getCurrent()));return ok(data);}
}

常见问题

  1. Controller中返回String类型, 会报类转换异常错误

    解决方案: 如果项目中String类型都是要统一包装的, 那就直接干掉所有StringHttpMessageConverter

    @Configuration
    public class StringHttpMessageConvertRemover implements WebMvcConfigurer {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.removeIf(it -> it instanceof StringHttpMessageConverter);}
    }
    

    或者不管String类型了

    @Override
    public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {if ("java.lang.String".equals(returnType.getParameterType().getName())) {return false;}return returnType.getContainingClass().getPackageName().startsWith("kim.nzxy.ly");
    }
    
  2. OpenAPI Knife4J等, 额外包装一层

    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import kim.nzxy.ly.common.res.PagingVO;
    import kim.nzxy.ly.common.res.Res;
    import org.apache.commons.lang3.reflect.TypeUtils;
    import org.springdoc.core.parsers.ReturnTypeParser;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.MethodParameter;
    import org.springframework.core.io.Resource;import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.Optional;/*** @author ly-chn* @since 2024/1/24 10:58*/
    @Configuration
    public class ApiDocOperationCustomizer {@Beanpublic ReturnTypeParser returnTypeParser() {return new ReturnTypeParser() {@Overridepublic Type getReturnType(MethodParameter methodParameter) {Type returnType = ReturnTypeParser.super.getReturnType(methodParameter);Class<?> parameterType = methodParameter.getParameterType();// 资源文件或者已经被包装了, 直接返回if (parameterType.isAssignableFrom(Resource.class) || parameterType.isAssignableFrom(Res.class)) {return returnType;}// 分页特殊处理, 转为PagingVO类if (parameterType.isAssignableFrom(Page.class) && returnType instanceof ParameterizedType) {Optional<Type> t = TypeUtils.getTypeArguments((ParameterizedType) returnType).values().stream().findFirst();Type type = t.orElse(Object.class);return TypeUtils.parameterize(Res.class, TypeUtils.parameterize(PagingVO.class, type));}// void转为Res<Object>if (parameterType.isAssignableFrom(void.class)) {return TypeUtils.parameterize(Res.class, Object.class);}// 包装Resreturn TypeUtils.parameterize(Res.class, returnType);}};}
    }
    
  3. 直接写Response 直接写 OutputStream 怎么办

    本来我也担心, 但是ResponseBodyAdvice类是Controller返回后, HttpMessageConverter执行转换之前执行, 所以无需担心直接写, 然后返回void的问题

    我做了这么一个测试, 不会走ResponseBodyAdvice, 但是此时Swagger/Knife4f, openapi就无能为力了, 因为没法从代码中获取是否有文件下载

    @GetMapping("void-with-byte")
    public void testVoidWithByte(HttpServletResponse response) throws IOException {response.setContentType("application/octet-stream;charset=utf-8");response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=application.yml");ClassPathResource resource = new ClassPathResource("application.yml");response.getOutputStream().write(resource.getContentAsByteArray());
    }
    

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

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

相关文章

Sketch怎么增加组件?

Sketch怎么增加组件&#xff1f;Sketch组件库经常使用&#xff0c;想要添加一些新的组件&#xff0c;该怎么添加呢&#xff1f;下面我们就来看看Sketch组件库添加新组建的技巧&#xff0c;详细请看下文介绍 打开电脑&#xff0c;找到sketch软件的图标&#xff0c;点击进入 新建…

计算机工作原理解析和解剖(基础版)

我们会从软件⼯程师的⻆度解释计算机是如何⼯作的&#xff0c;我们的主要⽬标既不是期待 ⼤家可以造出⾃⼰的计算机&#xff0c;也不是介绍如何编程&#xff0c;⽽是希望让⼤家了解计算机的核⼼⼯作机制后&#xff0c;打破计算机的神秘感&#xff0c;并且有利于理解我们平时编程…

【大数据】Flink 中的数据传输

Flink 中的数据传输 1.基于信用值的流量控制2.任务链接 在运行过程中&#xff0c;应用的任务会持续进行数据交换。TaskManager 负责将数据从发送任务传输至接收任务。它的网络模块在记录传输前会先将它们收集到 缓冲区 中。换言之&#xff0c;记录并非逐个发送的&#xff0c;而…

Stream实战-统计求和

Stream实战-统计 stream在开发中经常使用场景就是统计&#xff0c;再次记录一下实际开发中用的到统计&#xff0c;使用模拟数据。 需求如下&#xff1a; 代码如下: /*** map集合统计*/ public class StreamDemo4 {/*** 实体类*/DataAllArgsConstructorNoArgsConstructorclas…

Python模块与包:扩展功能、提高效率的利器

文章目录 一、引言1.1 模块与包对于Python开发的重要性1.2 Python作为拥有丰富生态系统的编程语言 二、为什么学习模块与包2.1 复用代码&#xff1a;利用现有模块与包加速开发过程2.2 扩展功能&#xff1a;通过模块与包提供的功能增强应用的能力 三、模块的使用3.1 导入模块&am…

UDF学习(二)数据访问宏

数据访问宏一 网格节点相关宏** NODE_X (v) 节点v的x方向的坐标 &#xff08;Node *v&#xff09; NODE_Y (v) 节点v的y方向的坐标 &#xff08;Node *v&#xff09; NODE_Z (v) 节点v的z方向的坐标 &#xff08;Node *v&#xff09; F_NODE (f,t,n) 获取节点 (face_t f, Thre…

Java基础进阶03-注解和单元测试

目录 一、注解 1.概述 2.作用 3.自定义注解 &#xff08;1&#xff09;格式 &#xff08;2&#xff09;使用 &#xff08;3&#xff09;练习 4.元注解 &#xff08;1&#xff09;概述 &#xff08;2&#xff09;常见元注解 &#xff08;3&#xff09;Target &#x…

vue3预览pdf文件的几种方法

vue3预览pdf集中方法 方法一&#xff1a; iframe&#xff1a;这种方法显示有点丑 <iframesrc"E:\\1.pdf"frameborder"0"style"width: 80%; height: 100vh; margin: auto; display: block"></iframe>方法二&#xff1a; 展示效果&…

【RA6M3 HMI Board线下培训笔记】 RT Thread实现物联网应用 ETH+MQTT+LVGL+RTOS 实现温湿度监测

【RA6M3 HMI Board线下培训笔记】 RT Thread实现物联网应用 ETHMQTTLVGLRTOS 实现温湿度监测 1. 序言 随着物联网技术的飞速发展&#xff0c;越来越多的生活场景变得越来越智能&#xff0c;网联化、智能化越来越成为主旋律。 值此之际&#xff0c;RT-Thread 和 瑞萨电子共同…

光耦驱动继电器电路图大全

光耦驱动继电器电路图&#xff08;一&#xff09; 注&#xff1a; 1U1-1脚可接12V&#xff0c;也可接5V&#xff0c;1U1导通&#xff0c;1Q1导通&#xff0c;1Q1-30V&#xff0c;线圈两端电压为11.7V. 1U1-1脚不接或接地&#xff0c;1U1不通&#xff0c;1Q1截止&#xff0c;1…

蓝桥OJ3694肖恩的投球游戏plus

二维差分 #include<bits/stdc.h> using namespace std;const int N 1e3 5; int a[N][N],d[N][N];int main() {int n, m, q;cin >> n >> m >> q;for (int i 1 ; i < n; i){for (int j 1; j < m; j){cin >> a[i][j];d[i][j] a[i][j] a…

webug存在的越权漏洞-水平越权以及垂直越权的漏洞复现(超详解)

越权漏洞-webug、 1.登录 账号&#xff1a;admin 密码&#xff1a;admin 2.进入逻辑漏洞 3.进入越权修改密码靶场 &#xff08;1&#xff09;输入账号密码 进入进去会发现没有权限进入 方法一&#xff1a; 这里我们只需要将 127.0.0.1:8080/control/a/auth_cross/cross_a…

Docker安装常用的开发组件

Docker安装Minio docker-compose.yml: version: 3 services:minio:image: minio/minio:RELEASE.2023-04-13T03-08-07Zcontainer_name: minioports:# api 端口- "9000:9000"# 控制台端口- "9001:9001"environment:# 时区上海TZ: Asia/Shanghai# 管理后台用…

pytorch实战-图像生成与对抗

1 概述 what&#xff1a;给定一句话&#xff0c;或一些要求&#xff0c;按要求生成需要的图像。 本篇总结主要包含反卷积和GAN&#xff08;generative adversial network, GAN&#xff09; 2 反卷积与图像生成 what&#xff1a;反卷积可以看成卷积的反操作&#xff0c;但不…

架构设计面试系列-01

1. 软件架构设计都有哪些基本原则? 1、开闭原则(OCP Open Close Principle) Software entities should be open for extension, but closed for modification. 定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。简单的说就是程序中类…

紫光展锐T760_芯片性能介绍_展锐T760安卓核心板定制

展锐T760核心板是一款基于国产5G芯片的智能模块&#xff0c;采用紫光展锐T760制程工艺为台积电6nm工艺&#xff0c;支持工艺具有出色的能效表现。其采用主流的44架构的八核设计&#xff0c;包括4颗2.2GHz A76核心和4颗A55核心设计&#xff0c;内存单元板载可达8GB Ram256GB ROM…

阿里云centos安装mysql,并修改初始密码

阿里云centos安装mysql&#xff0c;并修改初始密码 安装数据库、修改初始密码、并测试建立自己的数据库步骤1&#xff1a;创建数据库和用户步骤2&#xff1a;配置Nginx1. 创建新的站点配置文件2. 编辑配置文件3. 保存并退出编辑器4. 测试配置文件是否正确5. 重新加载 Nginx 以应…

uniapp vuecli项目融合[小记]:将多个项目融合,打包成一个小程序/App,拆分多个H5应用

前言&#xff1a; 目前两个uniapp vuecli开发的项目【A、B】&#xff0c;新规划的项目C&#xff1a;需要融合项目B 80%的功能模块&#xff0c;同时也需要涵盖项目A的所有功能模块。 应用需求&#xff1a; 1、新项目C【小程序】可支持切换到应用A/C界面【内部通过初始化、路由跳…

0125-1-vue3初体验

vue3尝鲜体验 初始化 安装vue/clinext&#xff1a; yarn global add vue/clinext # OR npm install -g vue/clinext然后在 Vue 项目运行&#xff1a; vue upgrade --next项目目录 vue3-template ├── index.html // html模板 ├── mock // mock数据 │ └── user.…

【学习】基础知识

1. 不同的维度表示 图像的维度表示&#xff08;H , W , C)&#xff1a; 对于一张彩色图像&#xff0c;通常有三个通道&#xff08;红、绿、蓝&#xff09;&#xff0c;每个通道都是一个二维矩阵。假设图像的高度为 H&#xff0c;宽度为 W&#xff0c;那么图像的维度表示为 (H…