衆所周知,後端存儲一些靈活可變的參數時,都會以字典的形式入庫,常見的業務中,下拉框,地區聯動等數據,都是這樣存儲的。前端
存時一時爽啊,可是用戶是看不懂字典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處理生成
}
複製代碼
cityCodeLabel、areaCodeLabel和provinceCodeLabel並無在實體中定義,可是接口中已經成功的增長上了,看來筆者的實現方式也算是靠譜。 這樣的方案只須要開發時,在出方向Vo加個註解就搞定了,同時也保留了原字段,仍是很方便的,Aop或者Filter等應該也是能夠實現相似效果,有更好的方案你們也能夠告訴筆者,互相交流。 最後別忘了,咱們是依靠緩存來優化本次的邏輯,是要進行緩存預熱的操做的哦~
~----------------------- 走過路過別錯過~ 原創不易,點個贊再走吧~