1. 程式人生 > >Json過濾器(基於spring自定義註解的方式,對欄位進行過濾)

Json過濾器(基於spring自定義註解的方式,對欄位進行過濾)

在實際的開發過程中,會經常遇到如下情景:

  • 後臺需要給前端返回JSON資料,但是查詢出來返回的資料裡面有很多屬性是不需要的

本文通過自定義註解的方式進行實現,對response進行攔截,通過註解引數,設定欄位資訊(即,過濾哪些欄位,保留哪些欄位),並將bean自動封裝為json,作為結果返回。
實現的具體程式碼可以在github中直接下載執行:https://github.com/MonkeyJJC/JsonFilter

整體思路:
(1)通過ResponseBodyAdvice實現在響應體寫出之前做一些處理;比如,修改返回值、加密等(此處即進行引數解析,設定過濾器的相關引數,為後續訊息轉換器處理準備)
(2)自定義訊息轉換器HttpMessageConverter
(3)自定義訊息轉換器的配置,通過@Bean定義HttpMessageConverter是向專案中新增訊息轉換器,如果Spring掃描到HttpMessageConverter型別的bean,就會將它自動新增到呼叫鏈中。否則spring會使用預設的處理器

Controll層:

@RestController
public class Demo {

    @GetMapping("user")
    @SerializeField(clazz = User.class, includes = {"name", "id"})
    public User user() {
        User user = new User(1L, "jjc", "123456");
        return user;
    }
}

其中@SerializeField就是自定義的註解,通過註解的方式宣告是對User類進行過濾操作,留下的欄位是{“name”, “id”},先看一下實現效果:

{
	id: 1,
	name: "jjc"
}

註解定義

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SerializeField {

    Class clazz();

    /**
     * 需要返回的欄位
     * @return
     */
    String[] includes() default {};

    /**
     * 需要去除的欄位
     * @return
     */
    String[] excludes() default {};
}

通過ResponseBodyAdvice對結果攔截

對通過@ResponseBody返回的結果進行攔截(返回的結果即beforeBodyWrite中的Object,此處進行攔截,並進行相關處理)

@ControllerAdvice
public class JsonFilterResponseBodyAdvice implements ResponseBodyAdvice {
    ·····
    @Override
    public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class converterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        JsonFilterObject jsonFilterObject = new JsonFilterObject();
        if (null == object) {
            return null;
        }
        if (!methodParameter.getMethod().isAnnotationPresent(SerializeField.class)) {
            return object;
        }
        /**
         * 處理類進行過濾處理
         */
        if (methodParameter.getMethod().isAnnotationPresent(SerializeField.class)) {
            /**
             * java.lang.reflect.Method.getAnnotation(Class <T> annotationClass)
             * @return: 如果存在於此元素,則返回該元素註釋指定的註釋型別,否則返回為null
             * 在處理類中,會對應強轉為對應的型別,如SerializeField類
             */
            Object obj = methodParameter.getMethod().getAnnotation(SerializeField.class);
            handleAnnotation(SerializeField.class, obj, jsonFilterObject);
        }
        /**
         * 不進行set,返回null,因為未初始化
         */
        jsonFilterObject.setObject(object);
        return jsonFilterObject;
    }

    private void handleAnnotation(Class clazz, Object object, JsonFilterObject jsonFilterObject) {
            /**
             * 獲取註解使用處的引數資訊,如@SerializeField(clazz = Address.class,includes = {"school", "home", "user"})
             * 獲取clazz型別及includes等資訊
             */
}

自定義訊息轉換器

在這裡插入圖片描述

可以看到,springMVC通過訊息轉換器,進行最後的輸出,通過ResponseBodyAdvice攔截之後,解析到相關資訊,初始化了過濾器,在此處,自定義的訊息轉換器中,呼叫過濾器,進行過濾相關操作:

public class JsonFilterHttpMessageConverter extends FastJsonHttpMessageConverter {

    private Charset charset;
    private SerializerFeature[] features;

    public JsonFilterHttpMessageConverter() {
        super();
        setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "json", UTF8),
                new MediaType("application", "*+json", UTF8),
                new MediaType("application", "jsonp", UTF8),
                new MediaType("application", "*+jsonp", UTF8)));
        setCharset(UTF8);
        setFeatures(SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteMapNullValue);
    }

    /**
     *
     * @param obj- the object to write to the output message
     * @param outputMessage- the HTTP output message to write to
     * @throws IOException- in case of I/O errors
     * @throws HttpMessageNotWritableException- in case of conversion errors
     * 官方地址:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/AbstractGenericHttpMessageConverter.html#writeInternal-T-org.springframework.http.HttpOutputMessage-
     */
    @Override
    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        if(obj instanceof JsonFilterObject){
            JsonFilterObject jsonFilterObject = (JsonFilterObject) obj;
            OutputStream out = outputMessage.getBody();
            SimpleSerializerFilter simpleSerializerFilter = new SimpleSerializerFilter(jsonFilterObject.getIncludes(), jsonFilterObject.getExcludes());
            /**
             * JSON序列化介面toJSONString
             * String toJSONString(Object, SerializeFilter, SerializerFeature...)
             */
            String text = JSON.toJSONString(jsonFilterObject.getObject(), simpleSerializerFilter, features);
            byte[] bytes = text.getBytes(this.charset);
            out.write(bytes);
        }else {
            /**
             * 未宣告@SerializeField註解
             */
            OutputStream out = outputMessage.getBody();
            String text = JSON.toJSONString(obj, this.features);
            byte[] bytes = text.getBytes(this.charset);
            out.write(bytes);
        }
    }

    @Override
    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    @Override
    public void setFeatures(SerializerFeature... features) {
        this.features = features;
    }
}

完整程式碼可以從github中直接下載執行:https://github.com/MonkeyJJC/JsonFilter

對於複雜bean的過濾,如bean中欄位包含容器或其他bean的情況,後續會對輪子進行相關功能完善。

對於spring訊息轉換器等概念可以參考:
SpringMVC原始碼剖析(五)-訊息轉換器HttpMessageConverter

Spring Boot:定製HTTP訊息轉換器

Spring MVC返回值處理踩坑筆記