如何優雅的轉義Java接口中的字典值

衆所周知,後端存儲一些靈活可變的參數時,都會以字典的形式入庫,常見的業務中,下拉框,地區聯動等數據,都是這樣存儲的。前端

存時一時爽啊,可是用戶是看不懂字典code的,因此取的時候須要把字典值再轉換回來。這樣取得時候就增長了工做量,常見過的方案基本有:單獨前端緩存維護一個字典文件,根據code在前端進行轉換。還有設計冗餘字段,或者直接在sql暴力join一波。java

「vt啊,xx項目代碼,關於字典code的sql關聯太多表了,你看看能不能改爲從緩存取,優化一下」,隨着PM的一句呼叫,立刻打開xx項目檢查主要的幾個接口查看sql寫法,果真相似這樣的風格...sql

沒錯了,筆者遇到的狀況就是 直接在sql暴力join一波的場景 閒話少說,既然收到優化任務,先乾爲敬,首先 暫時不要着急改目前穩定的代碼,因此我新增了一個 單表查詢字典值 沒有進行字典轉換的測試接口,返回結果以下:

{
	"name": "vt",  
	"age": 18,
	"areaCode": "20005",  // 地區code
	"cityCode": "201",  // 城市code
	"provinceCode": "03"  //省code
}
複製代碼

咱們先從這個測試接口來分析,思考怎樣使用緩存來獲取label,以及讓開發者,簡便的進行轉換。筆者的思路是,若是想要作到對代碼低入侵,而且修改少的話,須要從共通處理入手。接口從後臺返回到前端,是會通過json序列化的,因此我打算從序列化層入手。json

大體就像上圖,我打算在【一頓操做】這個層面,再加上個人又一頓操做,讓出方向的vo不新增字段,便可再返回一個對應的label。

筆者環境是SpringCloud,沒錯就是一堆小Springboot,那咱們就從Springboot默認的序列化框架Jackson入手,首先搞個配置,讓咱們把咱們自定義的SerializerModifier注入進去。後端

@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
	Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
	SimpleModule simpleModule = new SimpleModule().setSerializerModifier(new DictSerializerModifier());
	builder.modules(simpleModule);
	return builder;
}
複製代碼

既然是優化字典相關的,我就起名叫DictSerializerModifier,這個東西大體就是告訴框架,序列化修改你別幹了,我幹就好了, 這個DictSerializerModifier裏面是什麼呢,其實它也不是主要處理,以下:緩存

public class DictSerializerModifier extends Jdk8BeanSerializerModifier{
    @Override
    public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
        return new DictSerializer(null, (JsonSerializer<Object>) serializer);
    }
}
複製代碼

此處繼承BeanSerializerModifier也是能夠的,Jdk8BeanSerializerModifier是在java8中,對changeProperties方法有些內部優化,其實對業務影響不大。
咱們重要的是重寫modifySerializer方法,告訴框架咱們要用哪一個具體的類來處理序列化內容。則我新建了DictSerializer實現來處理具體的操做。細心的同窗能夠觀察到,返回實例時,我將默認的Serializer傳到了自定義的DictSerializer,這點是爲了若是沒有上文提到的【再次一頓操做】的狀況,則用默認的Serializer就好了。
先不用着急弄DictSerializer,咱們先自定義個註解,方便讓開發者指定,哪些字段想要進行什麼轉換:bash

ElementType type();

enum ElementType {
	PROVINCE, CITY, AREA
}
複製代碼

這樣做爲開發者的話,就像下面所示,只須要在字段上加上註解和想轉換的類型就能夠了。app

@DictAnnotation(type = AREA)  // 我想把這個code,增長AREA規則的轉換
private String areaCode;

@DictAnnotation(type = CITY)  // 我想把這個code,增長CITY規則的轉換
private String cityCode;
複製代碼

對了,咱們的目標是從緩存裏取,這個好說,筆者增長了一個RedisUtils.dictCodeToLabel方法,傳入字段註解中的枚舉類型具體的code,就能獲得label了。固然這不是重點,重點是DictSerializer是最主要的處理類,實現以下,我將細節已經寫在了註釋中框架

public class DictSerializer extends JsonSerializer<Object> implements ContextualSerializer {

    // 生成目標的Label字段後綴
    private static final String LABEL_SUFFIX = "VtTestLabel";
    // 自定義的註解中的枚舉內部類: PROVINCE, CITY, AREA等
    private DictAnnotation.ElementType type;
    // 上文提到的默認Serializer,從構造函數傳入
    private JsonSerializer<Object> defSerializer;

    public DictSerializer(DictAnnotation.ElementType type, JsonSerializer<Object> jsonSerializer) {
        this.defSerializer = jsonSerializer;
        this.type = type;
    }

    /**
     * 建立上下文關聯方法,實現ContextualSerializer便可
     * 這裏主要用戶獲取自定義註解,由於只靠serialize自己是獲取不到字段的註解信息的
     * */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        // 經過BeanProperty獲取自定義註解
        DictAnnotation.ElementType elementType = Optional.ofNullable(property).map(b -> b.getAnnotation(DictAnnotation.class))
                .map(d -> d.type()).orElse(null);
        // 若是該字段沒有標識該註解,elementType則爲null,使用默認的Serializer進行序列化,不然才用自定義的DictSerializer
        return elementType == null ? defSerializer : new DictSerializer(elementType, defSerializer);
    }

    /**
     * 主要方法,處理增長一個label字段的操做
     * */
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 字段自己的序列化操做,好比目前傳入的是areaCode=10001,則先序列化areaCode:10001
        defSerializer.serialize(value, gen, serializers);
        // 有自定義註解的type標識,咱們就增長一個對應的label,例:areaCode則 增長一個areaCodeVtTestLabel字段
        if (type != null) {
            // 原字段名 例:areaCode
            String fieldName = gen.getOutputContext().getCurrentName();
            // 字段對應的label 如 高新園區
            String codeLabel = RedisUtils.dictCodeToLabel(type, value.toString());
            // 寫入
            gen.writeStringField(fieldName.concat(LABEL_SUFFIX), codeLabel);
        }
    }
}
複製代碼

大功告成,讓咱們調一下上面的測試接口,看一下結果:ide

{
	"name": "vt",
	"age": 18,
	"areaCode": "20005",
	"areaCodeLabel": "高新園區",  //  由DictSerializer處理生成
	"cityCode": "201",
	"cityCodeLabel": "大連市",   //  由DictSerializer處理生成
	"provinceCode": "03",
	"provinceCodeLabel": "遼寧省"   //  由DictSerializer處理生成
}
複製代碼

cityCodeLabelareaCodeLabelprovinceCodeLabel並無在實體中定義,可是接口中已經成功的增長上了,看來筆者的實現方式也算是靠譜。 這樣的方案只須要開發時,在出方向Vo加個註解就搞定了,同時也保留了原字段,仍是很方便的,Aop或者Filter等應該也是能夠實現相似效果,有更好的方案你們也能夠告訴筆者,互相交流。 最後別忘了,咱們是依靠緩存來優化本次的邏輯,是要進行緩存預熱的操做的哦~

~----------------------- 走過路過別錯過~ 原創不易,點個贊再走吧~

相關文章
相關標籤/搜索