springboot2 jackson實現(xiàn)動態(tài)返回類字段方式
問題與需求
自從前后端分離的開發(fā)模式廣泛普及之后,JSON 便成為了端到端交互的首選數(shù)據(jù)結(jié)構(gòu)。
我們在使用 java 開發(fā)后端接口的時候,往往會出現(xiàn)我們一個類有十來個字段,但是前端使用到的可能就兩三個字段,產(chǎn)生大量冗余字段的情況,雖然對開發(fā)沒什么影響,但是感覺上就很不爽,并且好些敏感字段返回出去,會降低程序的安全性。
比如下面這個典型的例子:
@Getter @Setter public class User extends GlobalEntity { private Integer id; // 主鍵 private String roleCode; // 外鍵,角色代碼 private String name; // 用戶昵稱 private String account; // 賬號 private String pwd; // 密碼 }
- 我們在給前端返回用戶信息的時候,pwd 密碼字段肯定要過濾掉,不然會有安全隱患
- 我們在某些綁定場景,比如一個下拉選擇框選擇綁定用戶,這時候只需要 id 和 name 字段,其它字段就比較多余
- 但是我們在 service 層肯定都是調(diào)用相同的方法,這時候我們就會想要 在控制器層 控制該實體類 需要返回的字段
修改 @JsonFilter 的執(zhí)行機制
Jackson 本身支持
@JsonIgnore 只要在字段上加上該注解,就會被過濾掉,但是無法做到動態(tài),比如同一個類在 A 接口需要一個字段在B接口時不需要這個字段,該注解就無法實現(xiàn)
@JsonFilter 雖然支持動態(tài)配置過濾規(guī)則,但是這要求我們針對不同的類,寫入不同的過濾規(guī)則,而且像 springboot 程序中,我們一般是全局共享一個 ObjectMapper 對象,如果要對同一個類實現(xiàn)不同過濾規(guī)則,多線程情況下,會出現(xiàn)線程安全問題
筆者針對以上需求,結(jié)合 @JsonFilter 注解,修改并實現(xiàn)了自己過濾機制,從而實現(xiàn)動態(tài)過濾字段功能,廢話不多說,上代碼
實現(xiàn)一個自己的過濾規(guī)則類
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 { /** * 對于規(guī)則我們采用 ThreadLocal 封裝,防止出現(xiàn)線程安全問題 */ private static final ThreadLocal<Map<Class<?>, String[]>> include = new ThreadLocal<>(); /** * 清空規(guī)則 */ public static void clear() { include.remove(); } /** * 設(shè)置過濾規(guī)則 * @param clazz 規(guī)則 */ 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); } /** * 一個將過期的方法,但是目前還是需要實現(xiàn),拋個異常即可 */ @Deprecated @Override public BeanPropertyFilter findFilter(Object filterId) { throw new UnsupportedOperationException("不支持訪問即將過期的過濾器"); } /** * 重寫規(guī)律規(guī)則 */ @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; } }
將實現(xiàn)的過濾規(guī)則注入到 ObjectMapper
我們直接在啟動類中,注入自己的過濾規(guī)則
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 // 注明為微服務(wù)程序 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 內(nèi)容隨便填寫
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; // 菜單圖標(biāo) 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); }
- 查詢結(jié)果展示
抗干擾
上面的方式雖然實現(xiàn)了線程隔離,防止了線程安全問題,但是 springboot 的接口是以線程池的方式運行的,如果我們在一個線程給某個類設(shè)置了過濾的字段,下一次訪問如果也用到了該線程,并且沒對之前的規(guī)則做清理操作,那么他就會使用上一次的過濾規(guī)則,使接口出現(xiàn)奇怪的現(xiàn)象
解決方式一:
在所有接口前面,執(zhí)行 JsonFilter 的 clear 方法;
@PostMapping("menu/tree") public ResultVO<List<Menu>> tree() { JsonFilter.clear(); // 清理之前的過濾設(shè)置 List<Menu> vos = menuService.tree(); return ResultVO.ok("查詢成功", vos); }
這種方式就要求我們在所有控制層的方法都要執(zhí)行 JsonFilter.clear(); 語句,明顯代碼不夠優(yōu)雅
解決方法二:
使用 AOP 的前置攔截,執(zhí)行該代碼
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(); } }
@within(org.springframework.web.bind.annotation.RestController)
表示攔截所有帶 @RestController 注解的類,如果有其他需求,可以做適當(dāng)修改
擴展
JsonFilter.add(Menu.class, "id", "name");
- 針對這里的情況,我們還可以讓前端傳遞過來,實現(xiàn)一個接口讓前端控制返回哪些字段的功能
- 當(dāng)前筆者實現(xiàn)的是包含關(guān)系,只有被配置的字段才會出現(xiàn),如果出現(xiàn)一個類需要很多字段,只過濾一兩個字段,我們也可以通過修改 過濾規(guī)則類 JsonFilter 實現(xiàn),就擴展一下 apply 方法就好,很簡單。不過筆者不推薦,如果一個接口大部分字段都需要,那就全部返回好了,冗余一兩個字段也不是不能接受的
- 如果覺得在業(yè)務(wù)代碼里寫上上面的代碼不好看什么的,也可以通過 注解的方式實現(xiàn),然后用 AOP 攔截實現(xiàn),代碼邏輯差不多
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何利用Spring?MVC實現(xiàn)RESTful風(fēng)格
這篇文章主要介紹了如何利用Spring?MVC實現(xiàn)RESTful風(fēng)格,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java+opencv3.2.0實現(xiàn)hough圓檢測功能
這篇文章主要為大家詳細介紹了Java+opencv3.2.0實現(xiàn)hough圓檢測,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time方法示例
這篇文章主要給大家分享了關(guān)于JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time的方法,文中給出了詳細的示例代碼,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。2017-03-03