注册

关于我加了一行日志搞崩了服务这件小事(下)

接:关于我加了一行日志搞崩了服务这件小事(上)

// 方案一 - 这里会根据当前属性名和clazz来判断是否被忽略了,详见@JsonType注解
           boolean ignore = isJSONTypeIgnore(clazzpropertyName);
// 如果忽略了,就不再往下走了
           if (ignore) {
               continue;
          }
//此时根据属性和类获取对应的值对象。
           Field field = ParserConfig.getField(clazzpropertyName);
           JSONField fieldAnnotation = null;
           if (field != null) {
               //方案二 - 会获取属性对应的JSONField注解
               // 如果该注解的serialize属性是false,那么也不会继续往下去加载逻辑
               fieldAnnotation = field.getAnnotation(JSONField.class);
               if (fieldAnnotation != null) {
                   if (!fieldAnnotation.serialize()) {
                       continue;
                  }
//获取顺序
                   ordinal = fieldAnnotation.ordinal();
                   serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                   if (fieldAnnotation.name().length() != 0) {
                       //获取名字
                       propertyName = fieldAnnotation.name();
                       if (aliasMap != null) {
                           propertyName = aliasMap.get(propertyName);
                           if (propertyName == null) {
                               continue;
                          }
                      }
                  }
                   if (fieldAnnotation.label().length() != 0) {
                       label = fieldAnnotation.label();
                  }
              }
          }
           if (aliasMap != null) {
               propertyName = aliasMap.get(propertyName);
               if (propertyName == null) {
                   continue;
              }
          }
           //这里会新构建一个fieldInfo对象,并存放到fieldInfoMap中进行保存
           FieldInfo fieldInfo = new FieldInfo(propertyNamemethodfieldclazznullordinalserialzeFeatures,
                                               annotationfieldAnnotationlabel);
           fieldInfoMap.put(propertyNamefieldInfo);
      }
       //紧接着第二部分是关于isXXX的方法
       if (methodName.startsWith("is")) {
           if (methodName.length() < 3) {
               continue;
          }
           char c2 = methodName.charAt(2);
           String propertyName;
           if (Character.isUpperCase(c2)) {
               if (compatibleWithJavaBean) {
                   propertyName = decapitalize(methodName.substring(2));
              } else {
                   propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
              }
          } else if (...) {
          //同上面几乎一样,也是针对is_x这类特殊写法做了处理。
          }else {
               continue;
          }
           Field field = ParserConfig.getField(clazzpropertyName);
           if (field == null) {
               field = ParserConfig.getField(clazzmethodName);
          }
           JSONField fieldAnnotation = null;
           if (field != null) {
               //同样是对JSONField注解做处理。
               fieldAnnotation = field.getAnnotation(JSONField.class);
               if (fieldAnnotation != null) {
                   if (!fieldAnnotation.serialize()) {
                       continue;
                  }
                   ordinal = fieldAnnotation.ordinal();
                   serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                   if (fieldAnnotation.name().length() != 0) {
                       propertyName = fieldAnnotation.name();
                       if (aliasMap != null) {
                           propertyName = aliasMap.get(propertyName);
                           if (propertyName == null) {
                               continue;
                          }
                      }
                  }
                   if (fieldAnnotation.label().length() != 0) {
                       label = fieldAnnotation.label();
                  }
              }
          }
           if (aliasMap != null) {
               propertyName = aliasMap.get(propertyName);
               if (propertyName == null) {
                   continue;
              }
          }
           FieldInfo fieldInfo = new FieldInfo(propertyNamemethodfieldclazznullordinalserialzeFeatures,
                                               annotationfieldAnnotationlabel);
           fieldInfoMap.put(propertyNamefieldInfo);
      }
  }
//最后,又是对所有的常规属性做相应的处理,避免因为某个属性没写getX()方法而得不到序列化。整体的加载逻辑同上。
   for (Field field : clazz.getFields()) {
       if (Modifier.isStatic(field.getModifiers())) {
           continue;
      }
       JSONField fieldAnnotation = field.getAnnotation(JSONField.class);
       int ordinal = 0serialzeFeatures = 0;
       String propertyName = field.getName();
       String label = null;
       if (fieldAnnotation != null) {
           if (!fieldAnnotation.serialize()) {
               continue;
          }
           ordinal = fieldAnnotation.ordinal();
           serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
           if (fieldAnnotation.name().length() != 0) {
               propertyName = fieldAnnotation.name();
          }
           if (fieldAnnotation.label().length() != 0) {
               label = fieldAnnotation.label();
          }
      }
       if (aliasMap != null) {
           propertyName = aliasMap.get(propertyName);
           if (propertyName == null) {
               continue;
          }
      }

       if (!fieldInfoMap.containsKey(propertyName)) {
           FieldInfo fieldInfo = new FieldInfo(propertyNamenullfieldclazznullordinalserialzeFeatures,
                                               nullfieldAnnotationlabel);
           fieldInfoMap.put(propertyNamefieldInfo);
      }
  }

   List<FieldInfo> fieldInfoList = new ArrayList<FieldInfo>();

   boolean containsAll = false;
   String[] orders = null;

   JSONType annotation = clazz.getAnnotation(JSONType.class);
   if (annotation != null) {
       orders = annotation.orders();

       if (orders != null && orders.length == fieldInfoMap.size()) {
           containsAll = true;
           for (String item : orders) {
               if (!fieldInfoMap.containsKey(item)) {
                   containsAll = false;
                   break;
              }
          }
      } else {
           containsAll = false;
      }
  }

   if (containsAll) {
       for (String item : orders) {
           FieldInfo fieldInfo = fieldInfoMap.get(item);
           fieldInfoList.add(fieldInfo);
      }
  } else {
       for (FieldInfo fieldInfo : fieldInfoMap.values()) {
           fieldInfoList.add(fieldInfo);
      }
       if (sorted) {
           Collections.sort(fieldInfoList);
      }
  }
   return fieldInfoList;
}

代码有点长,听我一点点地慢慢解释。整个代码其实比较容易理解,我尝试从我们常规角度来理解下。fastJson组件的发明者认为,类中常见需要序列化的类型有三种:

1、getX()方法;

2、isX()方法;

3、没有写getX()方法的固有变量。

围绕这三种类型他做的事都是类似的。这里我们先以getX()方法为例子展开说明,要获取到所有的getX()方法,并对他们解析,主要分为以下四个步骤:

1、获取到所有的类下的方法信息

这个可以通过class<?>.getMethods()方法获得,如下是我coreData类的所有方法。

5c7d0ca52b794f3ca344447c92dac6ad~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?

2、判断符合规范的getXXX方法

在获取到了所有的method以后,我们自然需要判断哪些是符合规范的getXX方法。在组件中是这么判断的:

if (methodName.startsWith("get")) {
   //此时做相应的处理逻辑  
}

没错,就是这么粗暴简单。

3、根据JSONType判断是否需要加载

那么获取到这些方法就一定要加载了吗?当然不是!对于getter方法,fastJson会首先判断当前的属性,是否已被包含在了类的@JSONType(ignores = "xxx")下,如果包含在了其中,那么此时就不会去将该方法保存到待序列化的列表中。局限点在于该种写法只会对get方法生效,对于isXXX和普通属性是不会生效的。

// 方案一 - 这里会根据当前属性名和clazz来判断是否被忽略了,详见@JsonType注解
boolean ignore = isJSONTypeIgnore(clazzpropertyName);
// 如果忽略了,就不再往下走了
if (ignore) {
   continue;
}

b0e1f3c7d5b9484daac004b8b68936d2~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?

4、根据JSONField判断是否需要加载

什么?你说采用JSONType写一大堆不方便?fastJson自然也是想到了,那么此时就可以采用@JSONField(serialize = false)的方式在对单独的属性或方法进行标注。也能起到忽略的作用。

5797fd09b92c48988f6e7aca962ef4ee~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?

到此,以getXX()方法的解析判断就完成了,当然其中还有一些更为细致的判断逻辑,如跳过getMetaClass、返回值为空的跳过等等逻辑。但大体上已经不影响我们的分析了。isXXX和固有变亮的解析几乎相似。至此,我们已经大致了解了整个解析的原理。当然为了验证我们的逻辑的正确性,我对原本coreData的代码做了一下改造并进行了试验,具体内容如下所示:

@Data
@JSONType(ignores = "funcProperties")
public class CoreData {

   //正常的属性
   public String normalProperties = "normalProperties";

   /**
    * 以get开头的方法
    * @return
    */
   public String getFuncProperties(){
       double a = 2/0;
       return "getFuncProperties";
  }

   /**
    * 以is开头的方法
    * @return
    */
   @JSONField(serialize = false)
   public Boolean isType(){
       return true;
  }

   /**
    * 用于跳过,检查方法是否判断
    * @return
    */
   public String skipFuncProperties(){
       double a = 2/0;
       return "getFuncProperties";
  }
}

简要来说,这里对getFuncProperties方法,我才用了@JSONType(ignores = "funcProperties")将其进行忽略,而对于isType方法,我则用单个的@JSONField(serialize = false)对其进行忽略,如果我们的结论成立,那么此时应该只会保存一个normalProperties属性的输出,且不存在出现报错的情况。

1e35d9b3f8064248872689dd4e4773f3~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp?

事实证明,我们是对的。

案件总结与反思:

在经历了这次惨痛的教训之后,有哪些是值得我们深入关注去思考和反思的呢?

1、在编写方法的时候尽量避免才用getXXX、isXXX的方法进行书写,这会导致部分框架的解析出现问题。(这个点也是我曾经在JAVA开发手册中看到的,想必也是前人被坑过了。)

2、如果非要这样写,那么此时需要评估好当前这个方法是否需要被一些框架进行解析,如果不需要,尝试对这些类型属性添加基本的忽略操作。类似@JSONField(seralize = false)、@Trasient等注解。

3、避免在对象中参杂进复杂的业务逻辑。(当然这条并不一定正常,对于DDD的充血模型,有时候是需要一定的业务逻辑的混合的。)

吃一堑长一智,如此一来才能避免在未来犯下相同的错误呀~


作者:DrLauPen
来源:
juejin.cn/post/7134513215890784293



0 个评论

要回复文章请先登录注册