fastjson解析自定义get方法导致空指针问题

背景

为了在日志中把出入参打印出来,以便验证链路和排查问题,在日志中将入参用fastjson格式化成字符串输出,结果遇到了NPE。
在这里插入图片描述

问题复现

示例代码

public static void main(String[] args) {
   
    OrganizationId orgId = new OrganizationId();
    NodeName name = new NodeName("test");

    Node node = new Node();
    node.setName(name);
    node.setOrganizationId(orgId);

    System.out.println(JSONObject.toJSONString(node));
}

错误提示
在这里插入图片描述
发现是OrganizationId对象里的方法报空指针了,赶紧看一眼这个类:

public class OrganizationId {
   
    private String id;

    public Long getIdToLong() {
   
        return Long.valueOf(this.id);
    }
}

怎么会运行到 getIdToLong 方法呢?

问题排查

对 JSONObject.toJSONString 方法进行反复 debug 之后,终于发现了原因,以下是具体路径:

public static String toJSONString(Object object, 
                                  SerializeConfig config, 
                                  SerializeFilter[] filters, 
                                  String dateFormat,
                                  int defaultFeatures, 
                                  SerializerFeature... features) {
   
    SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);
    try {
   
        JSONSerializer serializer = new JSONSerializer(out, config);
        
        if (dateFormat != null && dateFormat.length() != 0) {
   
            serializer.setDateFormat(dateFormat);
            serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
        }

        if (filters != null) {
   
            for (SerializeFilter filter : filters) {
   
                serializer.addFilter(filter);
            }
        }

        serializer.write(object);

        return out.toString();
    } finally {
   
        out.close();
    }
}

往下到 serializer.write 方法:

 public final void write(Object object) {
   
        if (object == null) {
   
            out.writeNull();
            return;
        }

        Class<?> clazz = object.getClass();
        ObjectSerializer writer = getObjectWriter(clazz);

        try {
   
            writer.write(this, object, null, null, 0);
        } catch (IOException e) {
   
            throw new JSONException(e.getMessage(), e);
        }
    }

在这里插入图片描述
再到 getObjectWriter,注意入参create传了true:

public ObjectSerializer getObjectWriter(Class<?> clazz) {
   
    return getObjectWriter(clazz, true);
}

在 getObjectWriter 的核心具体实现中,走到了自定义对象序列化的流程:

// ......
if (create) {
   
    writer = createJavaBeanSerializer(clazz);
    put(clazz, writer);
}

createJavaBeanSerializer 往下到 TypeUtils.buildBeanInfo:

public final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
   
    SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
    if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
   
        return MiscCodec.instance;
    }

    return createJavaBeanSerializer(beanInfo);
}

在这里插入图片描述

在 buildBeanInfo 中,由于入参 fieldBased 是false,会走到 computeGetters 的逻辑:

List<FieldInfo> fieldInfoList = fieldBased
                ? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy) //
                : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);

在这里插入图片描述

看到 computeGetters 的名字,感觉八成是这里了,发现里面有一段逻辑是扫描以 get 开头的方法名,把方法后缀变成一个属性,后续在获取对应属性时,会去运行对应的 getter 方法:

if(methodName.startsWith("get")){
   
    // 省略...
    // 从方法名中解析出属性名
    propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
}

在这里插入图片描述
在这里插入图片描述

从上面这段代码可以获取到 propertyName 的值为 idToLong,并且对应的 fieldInfo 是 getIdToLong 方法。
到这里基本水落石出了,原来是fastjson序列化是扫描以 “get”(还有“is”) 开头的方法,并且从该方法名中提取属性,如果对应的方法中存在问题,那么这里就可能遇到对应的异常,就像本文遇到的NPE。

解决方案

1、 业务逻辑中处理:保证 node 对象中的 orgId 不为空,避免NPE。
2、日志打印中处理:不序列化整个对象,只打出关键信息,避开可能为空的字段。
3、 在调用JSON.toJSONString的时候,加上SerializerFeature.IgnoreNonFieldGetter参数,忽略掉所有没有对应成员变量(Field)的getter函数,可以正常序列化。

JSONObject.toJSONString(node, SerializerFeature.IgnoreNonFieldGetter)

4、 通过在函数上 getXxx() 增加@JSONField(serialize = false)注解,也能达到同样的效果。

@JSONField(serialize = false)
public Long getIdToLong() {
   
    return Long.valueOf(this.id);
}

computeGetters 中消费注解的代码:

JSONField annotation = method.getAnnotation(JSONField.class);

// ...

if(annotation != null){
   
    if(!annotation.serialize()){
   
        continue;
    }

// ...

if(methodName.startsWith("get")){
   
// ... 

总结

fastjson 将对象转为 string 时,会把以“get”开头的方法认为是属性的 getter,把 getXXX 方法后面的 XXX 变成一个属性,并通过 getXXX 方法去获取,如果get方法内存在异常逻辑,就可能报错。可以尽量避免使用JSON打日志。

附录

1、阿里巴巴开发规约
在这里插入图片描述

2、默认根据get方法进行序列化,根据java bean的定义,通过反射来获取,javaBean定义见:什么是JavaBean、bean?

相关推荐

  1. C语言 指针导致内存溢出

    2024-02-22 10:26:01       43 阅读
  2. Vue定义指令介绍及使用方法

    2024-02-22 10:26:01       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-22 10:26:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-22 10:26:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-22 10:26:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-22 10:26:01       20 阅读

热门阅读

  1. 虚拟地址空间与堆区

    2024-02-22 10:26:01       30 阅读
  2. hive rlike

    2024-02-22 10:26:01       26 阅读
  3. Hive 最全面试题及答案(基础篇)

    2024-02-22 10:26:01       26 阅读
  4. 网络安全专业术语中英对照指南

    2024-02-22 10:26:01       24 阅读
  5. R语言【sp】——定义控件光栅

    2024-02-22 10:26:01       21 阅读