当咱们在Java应用程序中需求对字典特点进行转化回来给前端时,如何简单、便利、而且优雅的处理是一个重要问题。在本文中,咱们将介绍如何运用Java中的序列化机制来优雅地完成字典值的翻译,从而简化开发。

什么是序列化

在Java中,序列化是将目标转化为字节省的进程,能够将这些字节省保存到文件中或经过网络进行传输。反序列化是将字节省转化为原始目标的进程。经过序列化和反序列化,咱们能够在不同的应用程序之间传递目标,也能够将目标保存到文件中以便今后运用。

运用序列化完成字典值的翻译

在Java中,咱们能够运用序列化机制来完成编码与其对应的意义的对应关系。具体过程如下:

  1. 定义一个字典注解与,例如:

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotationsInside
    @JsonSerialize(using = DictSerializer.class)
    public @interface Dict {
    ​
      /**
       * 字典类型
       * 比如在描绘学生的时分,1代表小学生 2代表初中生 3代表高中生 4代表大学生
       * 相同在描绘老师的时分,1代表语文老师 2代表数学老师 3代表英语老师 4代表体育老师
       * 相同的数值在不同类型下,代表意义不同,所以需求指定字典的类型
       */
      String dic();
    }
    
  2. 自定义注解结合承继JsonSerialize完成ContextualSerializer,完成回来结果转译:

    @Slf4j
    public class DictSerializer extends StdSerializer<Object> implements ContextualSerializer {
    ​
      private transient String dictCode;
    ​
      @Override
      public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty beanProperty){
        Dict dict = beanProperty.getAnnotation(Dict.class);
        return createContextual(dict.dic());
       }
    ​
      private JsonSerializer<?> createContextual(String dicCode) {
        DictSerializer serializer = new DictSerializer();
        serializer.setDictCode(dicCode);
        return serializer;
       }
    ​
      @Override
      public void serialize(Object value, JsonGenerator gen, SerializerProvider provider){
    ​
        String dictCode = getDictCode();
        if (StrUtil.isBlank(dictCode)) {
          return;
         }
        if (Objects.isNull(value)) {
          return;
         }
        try {
             // 由于序列化是每个目标都需求进行序列话操作,这儿为了削减网络IO,运用了 guava 的本地缓存(代码在下面)
          Map<String, String> dictMap = DictionaryConstants.DICTIONARY_CACHE.get(dictCode);
          if (dictMap.containsKey("nullValue")) {
              // 当本地缓存中不存在该类型的字典时,就调用查询方法,而且放入到本地缓存中(代码在下面)
            dictMap = translateDictValue(dictCode);
            DictionaryConstants.DICTIONARY_CACHE.put(dictCode, dictMap);
           }
          // 经过数据字典类型和value获取name
          String label = dictMap.get(value.toString());
          gen.writeObject(value);
          // 在需求转化的字段上添加@Dict注解,注明需求引证的code,后端会在回来值中添加filedName_dictText的key,前端只需求取对应的 filedName_dictText 就能够直接运用
          gen.writeFieldName(gen.getOutputContext().getCurrentName() + DictionaryConstants.DICT_TEXT_SUFFIX);
          gen.writeObject(label);
         } catch (Exception e) {
          log.error("错误信息:{}", e.getMessage(), e);
         }
       }
    ​
      private String getDictCode() {
        return dictCode;
       }
    ​
      private void setDictCode(String dictCode) {
        this.dictCode = dictCode;
       }
    ​
      protected DictSerializer() {
        super(Object.class);
       }
    }
    
  3. 将同类型的字典编码和对应的意义保存到一个Map中,例如:

    private Map<String, String> translateDictValue(String code) {
      if (StrUtil.isBlank(code)) {
       return null;
       }
        // Map<String, String> map = new HashMap<>();
      // map.put("1", "小学生");
      // map.put("2", "初中生");
      // map.put("3", "高中生");
      // map.put("4", "大学生");
     
        // 由于咱们公司选用微服务,然后字典模块独自拆分红一个服务,所以这儿运用Feign方法调用
      DictionaryFeignClient dictionaryFeign = SpringUtil.getBean(DictionaryFeignClient.class);
        return dictionaryFeign.dictionary(code);
    }
    
  4. 由于序列化是需求每个目标都进行序列话操作,假如回来的是集合的话,就会进行很多次序列化操作,此刻就需求对相同类型的字典进行缓存,我这儿运用了guava 的 LoadingCache 进行本地缓存(这儿可能有人会说了,假如这个时分字典值对应的意义修改了,你这个缓存岂不是会导致数据不正确,首先字典功用一般是办理端进行增删改操作,而且字典一旦定好了是不会容易修改的,假如你要硬杠,你赢了)。

    public class DictionaryConstants {
    ​
      /**
       * 字典翻译文本后缀
       */
      public static final String DICT_TEXT_SUFFIX = "_dictText";
    ​
      public static final LoadingCache<String, Map<String, String>> DICTIONARY_CACHE = CacheBuilder.newBuilder()
           .maximumSize(1000)
           .expireAfterWrite(30, TimeUnit.SECONDS)
           .expireAfterAccess(10, TimeUnit.SECONDS)
           .build(new CacheLoader<String, Map<String, String>>() {
            @Override
            public Map<String, String> load(String key) {
              Map<String, String> map = new HashMap<>();
              map.put("nullValue", "nullValue");
              return map;
             }
           });
    }
    

    这儿额定弥补一个小知识:

    • expireAfterWrite和expireAfterAccess都是Google Guava缓存库中的缓存过期战略。
    • expireAfterWrite表示缓存项在指定时刻后过期,不管缓存项是否被访问过。例如,假如咱们将缓存项的expireAfterWrite设置为10分钟,则缓存项在被添加到缓存中10分钟后过期,不管它是否被访问过。
    • 这两种过期战略能够独自或组合运用,以完成更灵敏的缓存战略。例如,咱们能够将缓存项的expireAfterWrite设置为10分钟,同时将expireAfterAccess设置为5分钟,这样缓存项将在10分钟后过期,或许在最近5分钟内没有被访问时过期,以先到者为准。
    • 运用expireAfterWrite和expireAfterAccess能够防止缓存中的数据过期时刻过长或过短,从而进步缓存的功率和可靠性。
  5. 比较于运用 aop 切面的方法,运用序列化的方法能更好的进行字典的翻译(由于 aop 方法很难处理目标中的特点的特点),例如:

    public class Company {
     private List<Staff> staffs;
    }
    ​
    public class Staff {
     private Integer age;
     private String name;
     @Dic(dic = "position")
     private String position;
     
    }
    

    在这种场景中,假如回来的是 Company 集合,运用 aop 切面方法就很难到达(开发难度与开发成本)与序列化方法相同的效果。

经过以上过程,咱们能够运用Java中的序列化机制来优雅地完成字典编码与其对应的意义的对应关系,从而简化编码数据的办理和保护。

总结

在本文中,咱们介绍了如何运用Java中的序列化机制来完成编码与其对应的意义的对应关系,从而简化编码数据的办理和保护。经过序列化和反序列化,咱们能够高效地传递和保存目标,进步应用程序的功率和可保护性。