欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot2 jackson實現(xiàn)動態(tài)返回類字段方式

 更新時間:2024年08月09日 11:29:29   作者:被遺忘的優(yōu)雅  
這篇文章主要介紹了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)文章

  • 淺談springboot如何保證多線程安全

    淺談springboot如何保證多線程安全

    這篇文章主要介紹了springboot如何保證多線程安全,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 一文帶你搞懂Java中Get和Post的使用

    一文帶你搞懂Java中Get和Post的使用

    這篇文章主要為大家詳細介紹了Java中Get和Post用法的相關(guān)資料,文中的示例代碼講解詳細,對我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下
    2022-11-11
  • Jdk中沒有jre文件夾怎么辦?如何解決?

    Jdk中沒有jre文件夾怎么辦?如何解決?

    這篇文章主要介紹了Jdk中沒有jre文件夾怎么辦?如何解決的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 如何利用Spring?MVC實現(xiàn)RESTful風(fēng)格

    如何利用Spring?MVC實現(xiàn)RESTful風(fēng)格

    這篇文章主要介紹了如何利用Spring?MVC實現(xiàn)RESTful風(fēng)格,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java+opencv3.2.0實現(xiàn)hough圓檢測功能

    Java+opencv3.2.0實現(xiàn)hough圓檢測功能

    這篇文章主要為大家詳細介紹了Java+opencv3.2.0實現(xiàn)hough圓檢測,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • 基于Java實現(xiàn)音頻播放功能

    基于Java實現(xiàn)音頻播放功能

    音頻播放是現(xiàn)代應(yīng)用程序中常見的一項功能,無論是在桌面應(yīng)用、游戲開發(fā)、媒體播放器、還是在廣告和提示音效中,下面我們就來看看如何使用Java實現(xiàn)簡單的音頻播放功能吧
    2025-03-03
  • JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time方法示例

    JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time方法示例

    這篇文章主要給大家分享了關(guān)于JAVA String轉(zhuǎn)化成java.sql.date和java.sql.time的方法,文中給出了詳細的示例代碼,相信對大家具有一定的參考價值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • Spring注入方式有哪些

    Spring注入方式有哪些

    你知道Spring的注入方式有哪幾種嗎?這篇文章主要為大家詳細介紹了Spring的注入方式,感興趣的小伙伴們可以參考一下
    2016-09-09
  • java匿名內(nèi)部類實例代碼詳解

    java匿名內(nèi)部類實例代碼詳解

    這篇文章主要介紹了java匿名內(nèi)部類實例代碼詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • SpringMVC ModelAndView的用法使用詳解

    SpringMVC ModelAndView的用法使用詳解

    這篇文章主要介紹了SpringMVC ModelAndView的用法使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12

最新評論