1. 程式人生 > 其它 >通過Jackson實現敏感詞過濾和型別轉換的功能

通過Jackson實現敏感詞過濾和型別轉換的功能

背景

目前正在做老專案遷移重構工作(Clojure轉Java),因歷史原因專案中給到端上的Json資料中和(id/user_id/order_id)等相關的id欄位需要以string型別給到端上。

已有clojure實現

祖傳專案是通過clojure實現的,clojure是一門弱語言和JavaScript一樣任何物件都是看做一個map,所以在clojure裡面針對這個long->string的功能,實現起來很簡單;只需要遞迴遍歷map判斷key是否存在於定義的set裡面即可,如果滿足條件,直接根據是否為list進行toString的處理;

需要實現的功能

實現一個Java方法,傳入一個物件進行返回序列化的Json字串。序列化的同時如果遇到滿足條件的欄位需要對該欄位進行 toString 處理,如果是List<Objec>需要轉換為 List<String>

如何通過Java實現?

因為專案中使用的是jackson序列化json,所以很容易想到的就是使用jackson提供的AnnotationIntrospector,在對應的欄位上新增 註解 ,然後自定義AnnotationIntrospector在匹配對應註解的時候返回自定義的Serializer。

tips:僅使用@JsonSerialize(using = ToStringSerializer.class)是不能處理 List<Long> -> List<String>這種情況的

AnnotationIntrospector 實現 long->toString 功能

 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.FIELD})
 public @interface CustomToString {
 
 }
 
 @Data
 public class XxxDTO{
   
     @CustomToString
     private  Long id;
 
     @CustomToString
     private  List<Long>  uids;
   
     private String desc;
   
 }
 
 class IdFieldToStringSerializer extends JsonSerializer<Object> {
 
         public static final IdFieldToStringSerializer INSTANCE = new IdFieldToStringSerializer();
 
         private IdFieldToStringSerializer() {
        }
 
         @Override
         public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
             if (value instanceof Collection) {
                 gen.writeStartArray();
                 for (Object item : (Collection) value) {
                     gen.writeString(String.valueOf(item));
                }
                 gen.writeEndArray();
                 return;
            }
             gen.writeString(String.valueOf(value));
        }
 
    }
 
 public class DefaultAnnotationIntrospector extends JacksonAnnotationIntrospector {
 
   @Override
     public Object findSerializer(Annotated a) {
         if(a.hasAnnotation(CustomToString.class)){
           return IdFieldToStringSerializer.INSTANCE;
        }
         return super.findSerializer(a);
    }
 
 }

擴充套件-AnnotationIntrospector 實現 欄位過濾 功能(也可以註解增加屬性,用於指定環境過濾等功能)

 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.FIELD})
 public @interface CustomFilter {
 
 }
 
 @Data
 public class XxxDTO{
   
     @CustomFilter
     private  Long id;
   
     private String desc;
   
 }
 
 public class DefaultAnnotationIntrospector extends JacksonAnnotationIntrospector {
 
       @Override
       public boolean hasIgnoreMarker(AnnotatedMember m) {
           if(m.hasAnnotation(CustomFilter.class)){
             return true;
          }
           return super.hasIgnoreMarker(m);
        }
 
 }

這裡欄位過濾其實功能只相當於 @JsonIgnore ,需要增加CustomToString的屬性,比如 env;然後在DefaultAnnotationIntrospector中根據env值再做判斷

AnnotationIntrospector實現分析

副作用

在後續開發過程中,在我們定義新的Object時,需要參照對應的field表對對應欄位增加註解。如果此時突然要對某個欄位過濾或者toString,那又得從頭挨著挨著檢查添加註解。增加開發成本和維護成本

優化升級

通過對 new ObjectMapper().writeValueAsString() 的執行流程 debug分析,發現物件的屬性都被解析成 BeanProperty了,然後會呼叫 SerializerProvider 的方法尋找合適的Serializer進行序列化

實現 long->toString 功能
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.BeanProperty;
 import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializationConfig;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
 import com.fasterxml.jackson.databind.ser.SerializerFactory;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.util.ObjectUtils;
 
 /**
  * @class-name: IdFieldToStringSerializerProvider
  * @description:
  * @author: Mr.Zeng
  */
 @Slf4j
 public class IdFieldToStringSerializerProvider extends DefaultSerializerProvider {
 
     private static final String FILED_SOURCE = "admin_id、id、msg_id、uid、uids、user_id、member_uids、parent_id";
 
     private static final Set<String> FILED_SET = new HashSet<>(
             Arrays.stream(FILED_SOURCE.split(";|,|;|,|、"))
                    .map(String::trim)
                    .filter(o -> !ObjectUtils.isEmpty(o))
                    .collect(Collectors.toSet())
    );
 
     public IdFieldToStringSerializerProvider() {
         super();
    }
 
     public IdFieldToStringSerializerProvider(IdFieldToStringSerializerProvider src) {
         super(src);
    }
 
     protected IdFieldToStringSerializerProvider(SerializerProvider src, SerializationConfig conf, SerializerFactory f) {
         super(src, conf, f);
    }
 
     @Override
     public DefaultSerializerProvider copy() {
         if (this.getClass() != IdFieldToStringSerializerProvider.class) {
             return super.copy();
        }
         return new IdFieldToStringSerializerProvider(this);
    }