SpringBoot+FastJson 优雅的过滤 Response Body

Spring 源码系列

1、Spring 学习之扩展点总结之后置处理器(一)
2、Spring 学习之扩展点总结之后置处理器(二)
3、Spring 学习之扩展点总结之自定义事件(三)
4、Spring 学习之扩展点总结之内置事件(四)
5、Spring 学习之扩展点总结之@Import(五)
6、Spring 学习之AOP总结之基础介绍(六)
7、Spring 学习之AOP总结之实现代理(七)
8、SpringBoot 学习之自动配置基本原理(八)
9、SpringBoot 学习之启动原理(九)
10、ElasticSearch学习随笔之SpringBoot Starter 操作
11、图数据库 Neo4j 学习之SpringBoot整合
12、SpringBoot 学习之常用 Filter / Advice 总结
13、SpringBoot+FastJson 优雅的过滤 Response Body

文章目录

  • Spring 源码系列
  • 前言
  • 一、思路简介
  • 二、FastJsonHttpMessageConverter 是什么?
  • 三、@*** 注解编写
    • 3.1 @ResponseJSON 注解
    • 3.2 @Ignore 注解
  • 四、JSONHttpMessageConverter 消息体处理实现
    • 4.1 JSONHttpMessageConverter 类及依赖
    • 4.2 writeInternal() 处理消息体
    • 4.3 toJSONString() 对象转换 JSON
    • 4.4 getSerializeFilter() 序列化Filter实现
    • 4.5 setApplicationContext() 设置上下文
  • 五、HandlerHolder 获取请求处理器 Handler 实现
  • 六、EntityClassPropertyFilter 序列化对象属性过滤器
  • 七、AppConfig 配置类让转换器生效
  • 八、测试结果
    • 8.1 测试接口
    • 8.2 请求测试
  • 八、小结

前言

还是那个问题,前两天在开发一个需求时遇到这么一个问题,就是对接口的返回体【Response Body】做一些处理,,猛然间居然还是手足无措,最后决定使用 ResponseBodyAdvice 通知来实现,所以总结了一下开发中常用的 Filter 和 Advice,也欢迎 码友 们指点一二,我也会在空闲时第一时间补充进去,博文见 《SpringBoot 学习之常用 Filter / Advice 总结》。
不过发现即便使用 Filter 或者 Advice 可以实现,但是不够优雅…
目前企业开发项目中基本上都是用 JSON 格式作为 API 响应体,我负责的项目亦是,并且使用的是 alibaba 的 fastjson,然后有前面大佬的杰作加上我的虚心学习之后,决定用 FastJson 扩展点【FastJsonHttpMessageConverter】来实现。

提示:只要按照思路简介的几步完成即可实现优雅的过滤消息体功能!

一、思路简介

在 Spring Boot 中,默认使用 Jackson 库来将返回体转换为 JSON 格式的数据。 Jackson 是一个流行的 JSON 处理库,Spring 框架中集成了它作为默认的 JSON 序列化和反序列化工具。
不过在我们的应用中也使用到了 FastJson 来对响应体或对象实体与 JSON 进行转换,既然使用到了了 FastJson,那我们就可以通过 FastJson 扩展来在转换 JSON 的过程中实现字段过滤,这样我觉得就优雅了许多。
FastJson 中有一个 FastJsonHttpMessageConverter 是 FastJson 中提供的消息【转换器】,我们就通过继承FastJsonHttpMessageConverter 并重写转换功能来实现返回体字段过滤。
具体有如下几点:

  1. 编写 @ResponseJSON@Ignore 注解,我们通过注解来配置每个接口返回的字段。
  2. 自定义转换器【JSONHttpMessageConverter】继承 FastJsonHttpMessageConverter 并且实现 ApplicationContextAware 接口(主要用于获取上下文)。
  3. 自定义HandlerHolder 类实现接口 HandlerInterceptor,主要用于获取到我们自己开发的请求处理器 Handler请求处理器)。
  4. 自定义 EntityClassPropertyFilter 过滤器实现 FastJson 提供的 PropertyPreFilter 过滤器并且 重写 apply(*) 方法 ,通过实现这个方法,可以自定义过滤规则,在序列化对象时,Fastjson 将会根据实现了 PropertyPreFilter 接口的对象的 apply 方法来判断哪些属性需要被序列化,哪些属性需要被过滤掉。
  5. 自定义 AppConfig 配置类让返回体转换器生效。

源码已提交到码云仓库,欢迎点击查看!!!

二、FastJsonHttpMessageConverter 是什么?

FastJsonHttpMessageConverter 是 FastJson 中提供的消息转换器,在 Spring Boot 中可以用来将对象转换为 JSON 格式的数据,用于处理接口返回体的数据,具体作用如下:

  1. JSON 转换,FastJsonHttpMessageConverter 实现了 Spring 框架中的 HttpMessageConverter 接口,能够将 JAVA 对象转换成 JSON 格式数据,以便通过 HTTP 返回给客户端。
  2. FastJson 配置,该转换器对 FastJson 进行配置,比如设置序列化特性、日期格式化等。
  3. 可以替代 Spring Boot 默认的 Jackson 转换器。

三、@*** 注解编写

按照思路简介 第 1 步,编写注解类,直接上代码。

3.1 @ResponseJSON 注解

package com.selftest.web.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseJSON {Ignore[] ignore() default {};boolean enable() default true;}

3.2 @Ignore 注解

package com.selftest.web.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ignore {Class<?> declaringClass();String[] propNames();boolean inverse() default false;}

四、JSONHttpMessageConverter 消息体处理实现

按照思路简介第 2 步,编写 JSONHttpMessageConverter 类,代码中都有注释,直接上代码。
这个类是实现响应体的主要部分,主要是实现消息体转换和配置,代码比较多,分开一段一段看。

4.1 JSONHttpMessageConverter 类及依赖

package com.selftest.web.http;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyPreFilter;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.selftest.web.RequestHandlerHolder;
import com.selftest.web.annotation.Ignore;
import com.selftest.web.annotation.ResponseJSON;
import com.selftest.web.filter.EntityClassPropertyFilter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;/*** JSON 数据转换,实现返回体字段过滤*/
public class JSONHttpMessageConverter extends FastJsonHttpMessageConverter implements ApplicationContextAware {/*** 应用上下文*/private ApplicationContext ctx;/*** 请求处理器 Handler 持有者 */@Resourceprivate RequestHandlerHolder requestHandlerHolder;private FastJsonConfig fastJsonConfig = new FastJsonConfig();此处代码下面依次呈现3.2 writeInternal() 方法3.3 toJSONString() 方法3.4 getSerializeFilter() 方法3.5 setApplicationContext() 方法
}

4.2 writeInternal() 处理消息体

	/*** 返回体处理* @param obj the object to write to the output message* @param outputMessage the HTTP output message to write to* @throws IOException* @throws HttpMessageNotWritableException*/@Overrideprotected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {//获取到返回流OutputStream out = outputMessage.getBody();String jsonString = toJSONString(obj);out.write(jsonString.getBytes(StandardCharsets.UTF_8));}

4.3 toJSONString() 对象转换 JSON

/*** 将对象转换为JSON字符串* @return*/private String toJSONString(Object obj){//获取到处理请求的 Handlerif(Objects.isNull(requestHandlerHolder)){return JSON.toJSONString(obj);}Object handler = requestHandlerHolder.getHandler();//如果 handler 为空或者 handler 不为空但是返回类型不是 entity 的if(Objects.isNull(handler)|| (Objects.nonNull(requestHandlerHolder.getHandlerMethod().getReturnType())&& requestHandlerHolder.getHandlerMethod().getReturnType().isAssignableFrom(ResponseEntity.class))){return JSON.toJSONString(obj);}//如果是处理方法,则获取到返回类型 ReturnType、annotationResponseJSON annotation = null;if (handler instanceof HandlerMethod) {HandlerMethod method = (HandlerMethod) handler;MethodParameter returnType = method.getReturnType();annotation = returnType.getMethodAnnotation(ResponseJSON.class);if (Objects.isNull(annotation)) {annotation = method.getMethodAnnotation(ResponseJSON.class);}}if(Objects.isNull(annotation) || (Objects.nonNull(annotation) && !annotation.enable())){return Objects.nonNull(obj) ? JSON.toJSONString(obj) : null;}// 获取到真正实现返回体过滤的序列化 filterSerializeFilter filter = getSerializeFilter(annotation);// 封装自定义 filter, 传入上面的 filter 和 自定义注解EntityClassPropertyFilter propertyFilter = new EntityClassPropertyFilter(filter, annotation);return JSON.toJSONString(obj, propertyFilter, this.fastJsonConfig.getSerializerFeatures());}

4.4 getSerializeFilter() 序列化Filter实现

/*** 获取序列化 filter* @param annotation* @return*/private SerializeFilter getSerializeFilter(ResponseJSON annotation) {if(Objects.nonNull(annotation)){Ignore[] ignoreFields = annotation.ignore();if(ignoreFields.length == 0){return null;}Map<Class<?>, Map<Boolean, Set<String>>> ignoreMap = new HashMap<>();for (Ignore ignore : ignoreFields) {Class<?> declaringClass = ignore.declaringClass();Map<Boolean, Set<String>> propNameMap = ignoreMap.get(declaringClass);if(Objects.isNull(propNameMap)) {propNameMap = new HashMap<>();ignoreMap.put(declaringClass, propNameMap);}boolean inverse = ignore.inverse();Set<String> propNameSet = propNameMap.get(inverse);if(CollectionUtils.isEmpty(propNameSet)){propNameSet = new HashSet<>();propNameMap.put(inverse, propNameSet);}for (String propName : ignore.propNames()) {propNameSet.add(propName);}}// 返回 属性预处理 Filter 实例,真正实现返回体字段过滤return (PropertyPreFilter) (jsonSerializer, object, name) -> {for (Map.Entry<Class<?>, Map<Boolean, Set<String>>> ignoreEntry : ignoreMap.entrySet()) {if (ignoreEntry.getKey().isAssignableFrom(object.getClass())) {Set<String> ignorePropNames = ignoreEntry.getValue().get(false);if (Objects.nonNull(ignorePropNames) && ignorePropNames.contains(name)) {return false;}ignorePropNames = ignoreEntry.getValue().get(true);if (Objects.nonNull(ignorePropNames) && !ignorePropNames.contains(name)) {return false;}}}return true;};}return null;}

4.5 setApplicationContext() 设置上下文

 /*** 获取应用上下文* @param applicationContext the ApplicationContext object to be used by this object* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.ctx = applicationContext;}

五、HandlerHolder 获取请求处理器 Handler 实现

按照思路简介 第 3 步,编写 HandlerHolder 类,代码中都有注释,直接上代码。
这个类主要是为了能在处理消息体处理的时候能获取到 Handler,因为我们的消息体处理是通过 Handler 方法上的 @ResponseJSON@Ignore 注解配置来实现的。

package com.selftest.web.interceptor;import com.selftest.web.RequestHandlerHolder;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;/*** 请求处理拦截器,主要获取 Handler* 通过 ThreadLocal 获取*/
public class HandlerHolder implements HandlerInterceptor, RequestHandlerHolder {/*** 通过 ThreadLocal 来暂存和获取 Handler*/private final ThreadLocal<Object> HANDLERS = new ThreadLocal<>();/*** 在请求是获取到 Handler 并存入 ThreadLocal* @param request current HTTP request* @param response current HTTP response* @param handler chosen handler to execute, for type and/or instance evaluation* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HANDLERS.set(handler);return true;}/*** 从 ThreadLocal 获取 Handler* @return* @param <T>*/@Overridepublic <T> T getHandler(){return (T) HANDLERS.get();}/*** 获取 Handler 的方法* @return*/@Overridepublic Method getHandlerMethod() {Object handler = getHandler();if (handler instanceof HandlerMethod) {return ((HandlerMethod) handler).getMethod();} else if (handler instanceof Method) {return (Method) handler;}return null;}
}

接口 RequestHandlerHolder 实现:

package com.selftest.web;import java.lang.reflect.Method;public interface RequestHandlerHolder {<T> T getHandler();Method getHandlerMethod();
}

六、EntityClassPropertyFilter 序列化对象属性过滤器

按照思路简介 第 4 步,编写 EntityClassPropertyFilter 类,代码中都有注释,直接上代码。

package com.selftest.web.filter;import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.PropertyPreFilter;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.selftest.web.annotation.ResponseJSON;import java.util.Objects;/*** 实现对象属性序列化 filter*/
public class EntityClassPropertyFilter implements PropertyPreFilter {private SerializeFilter filter;private ResponseJSON annotation;/*** 实例化* @param filter* @param annotation*/public EntityClassPropertyFilter(SerializeFilter filter, ResponseJSON annotation) {this.filter = filter;this.annotation = annotation;}/*** 自定义过滤规则, 判断哪些属性需要被序列化,哪些属性需要被过滤掉* @param jsonSerializer* @param object* @param name* @return*/@Overridepublic boolean apply(JSONSerializer jsonSerializer, Object object, String name) {Class<?> elementType = object.getClass();if (Objects.nonNull(elementType)) {while (!elementType.equals(Object.class)) {if (Objects.nonNull(filter) && filter instanceof PropertyPreFilter) {// 这里调用的是 JSONHttpMessageConverter 中 getSerializeFilter() 提供的 SerializeFilterif (!((PropertyPreFilter)filter).apply(jsonSerializer, object, name)) {return false;}}elementType = elementType.getSuperclass();}}return true;}
}

七、AppConfig 配置类让转换器生效

按照思路简介 第 5 步,编写 HandlerHolder 类,代码中都有注释,直接上代码。

package com.selftest.config;import com.selftest.web.http.JSONHttpMessageConverter;
import com.selftest.web.interceptor.HandlerHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 配置类*/
@Configuration
public class AppConfig implements WebMvcConfigurer {/*** 实例化请求 handler* @return*/@Bean(name = "requestHandlerHolder")public HandlerHolder requestHandlerHolder(){return new HandlerHolder();}/*** 返回体消息过滤 Bean* @return*/@Beanpublic JSONHttpMessageConverter jsonHttpMessageConverter(){return new JSONHttpMessageConverter();}/*** 注册请求拦截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(requestHandlerHolder());}
}

八、测试结果

8.1 测试接口

	@ResponseJSON(ignore = {@Ignore(declaringClass = User.class, propNames = {"age"})})@GetMapping("users")public User getUser(){User user = new User();user.setId(1);user.setName("phen");user.setAge(30);return user;}

8.2 请求测试

成功过滤字段

八、小结

在 Spring Boot 中,默认使用 Jackson 库来将返回体转换为 JSON 格式的数据。 Jackson 是一个流行的 JSON 处理库,Spring 框架中集成了它作为默认的 JSON 序列化和反序列化工具。
那在 FastJson 中提供的 FastJsonHttpMessageConverter 消息转换器,我们则可以通过对此转换器的重写来实现对请求体字段的过滤,在 FastJsonHttpMessageConverter 中的 getSerializeFilter() 方法返回了 PropertyPreFilter 实体则真正的实现了返回体字段的过滤,在 自定义类 JSONHttpMessageConverter 中的 toJSONString() 方法中我们可以看到这几句代码:

SerializeFilter filter = getSerializeFilter(annotation);
EntityClassPropertyFilter propertyFilter = new EntityClassPropertyFilter(filter, annotation);
return JSON.toJSONString(obj, propertyFilter, this.fastJsonConfig.getSerializerFeatures()) 

第二个参数 propertyFilter 则是我们自己定义的 Filter,并且实现了 FastJson 提供的序列化过滤器 PropertyPreFilterEntityClassPropertyFilter 重写了 apply() 并且回调了 getSerializeFilter() 获取到的 filter,通过 JSON.toJSONString() 并且传入三个参数,第一个参数是我们自己实现的请求处理器返回的实体对象 User,第二个参数则是我们自己实现的序列化 Filter,第三个参数就是 FastJson 的默认的 SerializerFeature。

到这里,Spring Boot + FastJson 优雅的实现消息体字段的过滤就基本完成了,可以通过 debug 方式来看一下具体请求过滤是如何执行的,调用逻辑是什么。


江湖必有大佬藏,如有指点可别藏!
本文示例亲自手敲代码并且执行通过。
如有问题,还请指教。 评论区告诉我!!!一起学习一起进步!!!

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

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

相关文章

【Stm32-F407】Keil uVision5 下新建工程

①双击鼠标左键打开Keil uVision5&#xff0c;选择 Project 下的 New uVision Project &#xff1b; ②在期望的文件夹下创建一个工程&#xff0c;并按如下要求操作&#xff1b; ③添加文件类型&#xff0c;按如下要求操作 ④如有需要可添加相关启动文件在工程文件夹下并添加到…

数据结构 AVL树概念以及实现插入的功能(含Java代码实现)

为啥要有avl树 avl树是在二叉搜索树下的一种进阶形式,是为了防止二叉搜索树在极端情况下产生的链表化的场景,从而在二叉搜索树的基础上,加上了某些条件来阻止这种极端情况的产生,但不是保证完全平衡,而是放开了一定的条件,使得这种情况不那么难以满足.(条件:左右子树的高度差的…

华为组播配置案例

igmp-snooping主要用于生成二层组播表项&#xff0c;防止交换机全部接口都发组播报文 PC端配置&#xff1a; 组播源配置&#xff1a; R1 interface GigabitEthernet0/0/0 ip address 10.0.0.1 255.255.255.0 pim dm interface GigabitEthernet0/0/1 ip address 192.168.0…

【深度学习目标检测】八、基于yolov5的抽烟识别(python,深度学习)

YOLOv5是目标检测领域一种非常优秀的模型&#xff0c;其具有以下几个优势&#xff1a; 1. 高精度&#xff1a;YOLOv5相比于其前身YOLOv4&#xff0c;在目标检测精度上有了显著的提升。YOLOv5使用了一系列的改进&#xff0c;如更深的网络结构、更多的特征层和更高分辨率的输入图…

GD32烧录第一盏灯

学习目标 掌握基本开发流程掌握程序编译掌握程序烧录掌握GPIO初始化流程学习内容 开发流程 项目新建代码编写程序烧录验证结果需求分析 点亮LED1灯,并且闪烁。 项目新建 📎GD32Template.zip 附件为模板代码,解压后修改项目名称。 进入Project

【从零开始学习--设计模式--策略模式】

返回首页 前言 感谢各位同学的关注与支持&#xff0c;我会一直更新此专题&#xff0c;竭尽所能整理出更为详细的内容分享给大家&#xff0c;但碍于时间及精力有限&#xff0c;代码分享较少&#xff0c;后续会把所有代码示例整理到github&#xff0c;敬请期待。 此章节介绍策…

数据仓库与数据挖掘小结

更加详细的只找得到pdf版本 填空10分 判断并改错10分 计算8分 综合20分 客观题 填空10分 判断并改错10分--错的要改 mooc中的--尤其考试题 名词解释12分 4个&#xff0c;每个3分 经常碰到的专业术语 简答题40分 5个&#xff0c;每道8分 综合 画roc曲线 …

怎么将文件变为可执行文件

怎么将文件变为可执行文件 在Unix/Linux系统中&#xff0c;要将一个文件变为可执行文件&#xff0c;你需要使用chmod命令。以下是基本的步骤&#xff1a; 打开终端&#xff1a;使用你系统中的终端或命令行界面。 使用 cd 命令切换到包含你的文件的目录。例如&#xff1a; bash …

106基于matlab的粒子群算法与 Simulink 模型之间连接的桥梁是粒子(即 PID 控制器参数)和该粒子对应的适应值(即控制系统的性能指标)

基于matlab的粒子群算法与 Simulink 模型之间连接的桥梁是粒子&#xff08;即 PID 控制器参数&#xff09;和该粒子对应的适应值&#xff08;即控制系统的性能指标&#xff09;。优化过程如下&#xff1a;PSO 产生粒子群&#xff08;可以是初始化粒子群&#xff0c;也可以是更新…

阿里云部署k8s with kubesphere

阿里云ESC 创建实例 填入密码即可 云上的防火墙相关设置就是安全组 vpc 专有网络 划分私有ip 子网 vpc 隔离环境域 不同的vpc下 即使相同的子网也不互通 使用交换机继续划分子网 停止 释放 不收钱 k8s 服务器 4核8G*1 8核16G *2 git 创建凭证 pipeline 发邮箱 (p124)…

数据结构:直接选择排序和堆排序

直接选择排序&#xff1a; 这里我用两个变量同时找出最小值和最大值。 注意&#xff1a;若begin为最大值&#xff0c;maxi即为最大值的下标&#xff0c;若将最小值与其交换&#xff0c;最大值的下标此时就不再是maxi&#xff0c;而变为mini了&#xff0c;故此时要调整maxi的位…

关于pycharm无法进入base界面的问题

问题&#xff1a;terminal输入activate无法进入base 解决方案 1.Cortana这边找到Anaconda Prompt右击进入文件所在位置 2. 右击进入属性 3. 复制cmd.exe开始到最后的路径 cmd.exe "/K" C:\ProgramData\anaconda3\Scripts\activate.bat C:\ProgramData\anaconda3 …