springboot2 jackson 实现动态返回类字段
问题与需求
自从前后端分离的开发模式广泛普及之后,JSON 便成为了端到端交互的首选数据结构。
我们在使用 java 开发后端接口的时候,往往会出现我们一个类有十来个字段,但是前端使用到的可能就两三个字段,产生大量冗余字段的情况,虽然对开发没什么影响,但是感觉上就很不爽,并且好些敏感字段返回出去,会降低程序的安全性。
比如下面这个典型的例子
我们在给前端返回用户信息的时候,pwd 密码字段肯定要过滤掉,不然会有安全隐患
我们在某些绑定场景,比如一个下拉选择框选择绑定用户,这时候只需要 id 和 name 字段,其它字段就比较多余
但是我们在 service 层肯定都是调用相同的方法,这时候我们就会想要 在控制器层 控制该实体类 需要返回的字段
修改 @JsonFilter 的执行机制
Jackson 本身支持
@JsonIgnore 只要在字段上加上该注解,就会被过滤掉,但是无法做到动态,比如同一个类在 A 接口需要一个字段在B接口时不需要这个字段,该注解就无法实现
@JsonFilter 虽然支持动态配置过滤规则,但是这要求我们针对不同的类,写入不同的过滤规则,而且像 springboot 程序中,我们一般是全局共享一个 ObjectMapper 对象,如果要对同一个类实现不同过滤规则,多线程情况下,会出现线程安全问题
笔者针对以上需求,结合 @JsonFilter 注解,修改并实现了自己过滤机制,从而实现动态过滤字段功能,废话不多说,上代码
实现一个自己的过滤规则类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | package com.hwq.common.api.config; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.BeanPropertyFilter; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.PropertyFilter; import com.fasterxml.jackson.databind.ser.PropertyWriter; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import java.util.HashMap; import java.util.Map; public class JsonFilter extends FilterProvider { /** * 对于规则我们采用 ThreadLocal 封装,防止出现线程安全问题 */ private static final ThreadLocal<Map<Class<?>, String[]>> include = new ThreadLocal<>(); /** * 清空规则 */ public static void clear() { include.remove(); } /** * 设置过滤规则 * @param clazz 规则 */ public static void add(Class<?> clazz, String ... fields) { Map<Class<?>, String[]> map = include.get(); if (map == null ) { map = new HashMap<>(); include.set(map); } map.put(clazz, fields); } /** * 一个将过期的方法,但是目前还是需要实现,抛个异常即可 */ @Deprecated @Override public BeanPropertyFilter findFilter(Object filterId) { throw new UnsupportedOperationException( "不支持访问即将过期的过滤器" ); } /** * 重写规律规则 */ @Override public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) { return new SimpleBeanPropertyFilter() { @Override public void serializeAsField( Object pojo, JsonGenerator jg, SerializerProvider sp, PropertyWriter pw ) throws Exception { if (apply(pojo.getClass(), pw.getName())) { pw.serializeAsField(pojo, jg, sp); } else if (!jg.canOmitFields()) { pw.serializeAsOmittedField(pojo, jg, sp); } } }; } /** * 判断该字段是否需要,返回 true 序列化,返回 false 则过滤 * @param type 实体类类型 * @param name 字段名 */ public boolean apply(Class<?> type, String name) { Map<Class<?>, String[]> map = include.get(); if (map == null ) { return true ; } String[] fields = map.get(type); for (String field : fields) { if (field.equals(name)) { return true ; } } return false ; } } |
将实现的过滤规则注入到 ObjectMapper
我们直接在启动类中,注入自己的过滤规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package com.hwq.admin.back; import com.fasterxml.jackson.databind.ObjectMapper; import com.hwq.common.api.config.JsonFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication // 注明为微服务程序 public class AdminBackApp { public static void main(String[] args) { ConfigurableApplicationContext app = SpringApplication.run(AdminBackApp. class ); ObjectMapper objectMapper = app.getBean(ObjectMapper. class ); objectMapper.setFilterProvider( new JsonFilter()); } } |
测试使用
实体类,需要加上 @JsonFilter(“f”) 注解,f 内容随便填写
package com.hwq.common.api.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.hwq.common.api.model.common.GlobalEntity;
import lombok.Getter;
import lombok.Setter;
@JsonFilter("f")
@TableName("t_menu")
@Getter
@Setter
public class Menu extends GlobalEntity {
@TableId(value = "id", type = IdType.AUTO)
private Integer id; // 主键
private Integer pid; // 菜单的主键,一级菜单为0
private String name; // 菜单名称
private String path; // 菜单路径
private String icon; // 菜单图标
private Integer ordered; // 菜单序号
}
接口使用
@PostMapping("menu/list")
public ResultVO<Object> list() {
// 这里配置某个类需要返回的字段,如果有多个类,可以多次 add
JsonFilter.add(Menu.class, "id", "name", "icon", "path");
List<Menu> list = menuService.list();
return ResultVO.ok("查询成功", list);
}
查询结果展示
抗干扰
上面的方式虽然实现了线程隔离,防止了线程安全问题,但是 springboot 的接口是以线程池的方式运行的,如果我们在一个线程给某个类设置了过滤的字段,下一次访问如果也用到了该线程,并且没对之前的规则做清理操作,那么他就会使用上一次的过滤规则,使接口出现奇怪的现象
解决方式一:在所有接口前面,执行 JsonFilter 的 clear 方法;
1 2 3 4 5 6 | @PostMapping ( "menu/tree" ) public ResultVO<List<Menu>> tree() { JsonFilter.clear(); // 清理之前的过滤设置 List<Menu> vos = menuService.tree(); return ResultVO.ok( "查询成功" , vos); } |
这种方式就要求我们在所有控制层的方法都要执行 JsonFilter.clear(); 语句,明显代码不够优雅
解决方法二:使用 AOP 的前置拦截,执行该代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.hwq.admin.back.config; import com.hwq.common.api.config.JsonFilter; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class JsonFilterAop { /** * (1)@annotation:用来拦截所有被某个注解修饰的方法 * (2)@within:用来拦截所有被某个注解修饰的类 * (3)within:用来指定扫描的包的范围 */ @Before ( "@within(org.springframework.web.bind.annotation.RestController)" ) public void doBefore() { JsonFilter.clear(); } } |
扩展
1 | JsonFilter.add(Menu. class , "id" , "name" ); |
针对这里的情况,我们还可以让前端传递过来,实现一个接口让前端控制返回哪些字段的功能
当前笔者实现的是包含关系,只有被配置的字段才会出现,如果出现一个类需要很多字段,只过滤一两个字段,我们也可以通过修改 过滤规则类 JsonFilter 实现,就扩展一下 apply 方法就好,很简单。不过笔者不推荐,如果一个接口大部分字段都需要,那就全部返回好了,冗余一两个字段也不是不能接受的
如果觉得在业务代码里写上上面的代码不好看什么的,也可以通过 注解的方式实现,然后用 AOP 拦截实现,代码逻辑差不多
__EOF__

本文链接:https://www.cnblogs.com/caicz/p/16828636.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律