首页 > 编程语言 >【Java】使用fastjson进行序列化时出现空指针异常问题研究

【Java】使用fastjson进行序列化时出现空指针异常问题研究

时间:2023-07-29 22:44:51浏览次数:39  
标签:fastjson java alibaba JSON Java 序列化 com public

最近在使用fastjson的JSONObject.toJSONString()方法将bean对象转为字符串的时候报如下错误:

com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.58, class com.sun.proxy.$Proxy395, fieldName : 0
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:523)
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:160)
	at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
	at com.alibaba.fastjson.serializer.ASMSerializer_5_Xtsaxx.write(Unknown Source)
	at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:285)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:740)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:678)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:643)
	// ...省略一些业务代码
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: null
	at com.sun.proxy.$Proxy395.isBooleanValid(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:491)
	at com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:149)
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:291)
	... 21 common frames omitted

根据错误信息定位到JavaBeanSerializer的291行代码,调用了getPropertyValueDirect方法获取属性值,如果出现异常,会进入到catch中的处理逻辑:
(1)如果设置了忽略Getter没有对应字段的情况(SerializerFeature.IgnoreErrorGetter),属性值置为NULL;
(2)如果没有设置忽略Getter没有对应字段的情况,向上抛出异常,问题基本就出现在这里了,从错误信息中可以看出调用的isBooleanValid方法抛出的,那么猜测应该是该方法没有对应的字段造成的

public class JavaBeanSerializer extends SerializeFilterable implements ObjectSerializer {    
  protected void write(JSONSerializer serializer, //
                      Object object, //
                      Object fieldName, //
                      Type fieldType, //
                      int features,
                      boolean unwrapped
    ) throws IOException {
        SerializeWriter out = serializer.out;
        // ....
        try {
                if (notApply) {
                    propertyValue = null;
                } else {
                    try {
                        // 这里是291行代码
                        // 获取属性值,这里的实现是执行GETTER方法获取属性值
                        propertyValue = fieldSerializer.getPropertyValueDirect(object);
                    } catch (InvocationTargetException ex) {
                        errorFieldSerializer = fieldSerializer;
                        // 如果设置忽略Getter没有对应字段的情况
                        if (out.isEnabled(SerializerFeature.IgnoreErrorGetter)) {
                            propertyValue = null; // 值置为NULL
                        } else {
                            throw ex; // 否则抛出异常
                        }
                    }
                }
        } catch (Exception e) {
            String errorMessage = "write javaBean error, fastjson version " + JSON.VERSION;
            // 这里是第523行代码,向上抛出异常
            throw new JSONException(errorMessage, cause);
        } finally {
            serializer.context = parent;
        }
    }
}

进入到FieldSerializer的getPropertyValueDirect方法,可以看到调用了FieldInfo的get方法获取属性值,get方法中如果method(也就是GETTER方法)不为空,就通过反射执行方法来获取返回值:

public class FieldSerializer implements Comparable<FieldSerializer> {
    public final Method  method;
    
    public Object getPropertyValueDirect(Object object) throws InvocationTargetException, IllegalAccessException {
        // fieldInfo是具体的字段,这里调用对应的方法,从对象object中获取字段的值
        Object fieldValue =  fieldInfo.get(object);
        if (persistenceXToMany && !TypeUtils.isHibernateInitialized(fieldValue)) {
            return null;
        }
        return fieldValue;
    }
}

public class FieldInfo implements Comparable<FieldInfo> {
    public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
        // 如果method不为空,执行方法获取返回值
        return method != null
                ? method.invoke(javaObject) // 通过反射执行方法
                : field.get(javaObject); 
    }
}

对于出现错误的原因现在已经比较清楚,根据GETTER方法找不到对应的字段导致的,解决方式也比较简单,在序列化的时候通过SerializerFeature.IgnoreErrorGetter设置忽略即可:

JSONObject.toJSONString(bean, SerializerFeature.IgnoreErrorGetter);

虽然解决问题比较简单,但我们还是来模拟一下,看下在什么情况下会出现这种错误:

(1)首先定义了一个User接口,接口中定义了是否合法以及用户名的get/set方法,当然还定义了一个isBooleanValid,问题就出现这个方法上,它同样用来返回是否合法,只不过返回值是bollean类型,由于是公司其他团队封装的一些公用组件,猜测添加isBooleanValid方法可能是为了处理某种情况吧:

/**
 * User接口
 */
public interface IUser {

    public Integer getValid();

    public void setValid(Integer nValid);

    public String getName();

    public void setName(String cName);

    default boolean isBooleanValid() {
        Integer valid = getValid();
        return valid != null && valid == 1;
    }
}

(2)创建User的具体实现类,假如有一个管理员类型的用户,它添加了valid和name成员变量,并实现了接口中的get/set方法,这里可以看到并没有booleanValid字段:

public class AdminUser implements IUser {

    private Integer valid;

    private String name;

    @Override
    public Integer getValid() {
        return valid;
    }

    @Override
    public void setValid(Integer nValid) {
        this.valid = nValid;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String cName) {
        this.name = cName;
    }
}

(3)创建DTO,前端提交的参数中包含用户的信息:

public class SubmitDTO {
    
    private IUser user;

    public IUser getUser() {
        return user;
    }

    public void setUser(IUser user) {
        this.user = user;
    }
}

(4)测试

情况一(正常序列化)

手动创建AdminUser,并设置相应的信息,调用JSONObject.toJSONString方法将对象转为JSON字符串:

public class Test {

    public static void main(String[] args) {
        AdminUser user = new AdminUser();
        user.setName("admin");
        user.setValid(1);
        SubmitDTO submitDTO = new SubmitDTO();
        submitDTO.setUser(user);
        // 情况一
        System.out.println(JSONObject.toJSONString(submitDTO));
    }
}

输出:

{"user":{"booleanValid":true,"name":"admin","valid":1}}

对于这种情况,已经明确知道IUser的具体实现类为AdminUser:

情况二(出现异常)

由于参数是前端传入的JSON字符串,后端在接收参数的时候将字符串解析为对应的实体对象,这里我们直接用字符串模拟:

public class Test {

    public static void main(String[] args) {

        String submitStr = "{\"user\":{\"booleanValid\":true,\"name\":\"admin\",\"valid\":1}}";
        SubmitDTO submitDTO1 = JSON.parseObject(submitStr, SubmitDTO.class);
        System.out.println(JSONObject.toJSONString(submitDTO1));
    }
}

通过parseObject方式解析后的对象,对于接口是通过动态代理创建对应的实现类,此时调用JSONObject.toJSONString就会出现上面的错误:

Exception in thread "main" com.alibaba.fastjson.JSONException: write javaBean error, fastjson version 1.2.58, class com.sun.proxy.$Proxy0, fieldName : user
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:544)
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:154)
	at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:360)
	at com.alibaba.fastjson.serializer.ASMSerializer_1_SubmitDTO.write(Unknown Source)
	at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:312)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:793)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:731)
	at com.alibaba.fastjson.JSON.toJSONString(JSON.java:688)
	at Test.main(Test.java:49)
Caused by: java.lang.NullPointerException
	at com.sun.proxy.$Proxy0.isBooleanValid(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.alibaba.fastjson.util.FieldInfo.get(FieldInfo.java:571)
	at com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValueDirect(FieldSerializer.java:143)
	at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:287)
	... 8 more

当然解决方式上面已经提到过,设置SerializerFeature.IgnoreErrorGetter即可:

public class Test {

    public static void main(String[] args) {
        String submitStr = "{\"user\":{\"booleanValid\":true,\"name\":\"admin\",\"valid\":1}}";
        SubmitDTO submitDTO1 = JSON.parseObject(submitStr, SubmitDTO.class);
        System.out.println(JSONObject.toJSONString(submitDTO1, SerializerFeature.IgnoreErrorGetter));
    }
}

输出:

{"user":{"name":"admin","valid":1}}

不过对于使用动态代理生成实现类的方式,在执行GETTER方法时,如果没有对应的字段会报错的问题有待研究。

标签:fastjson,java,alibaba,JSON,Java,序列化,com,public
From: https://www.cnblogs.com/shanml/p/17590693.html

相关文章

  • Java面试题 P15:Redis篇:面试场景
    Redis内容: 面试题总结: ......
  • chrome 翻译功能 与 禁止/拦截JavaScript Disable JavaScript
    在chrome://settings/content/siteDetails?site=中禁止JavaScript再恢复无法使用翻译功能但是在devtools中DisableJavaScript,再取消选中,可以翻译 ......
  • 学习Java的第9天
    类型转换由于Java是强类型语言,所有要进行有些运算的时候的,需要用到类型转换。运算中,不同类型的数据先转化为同一类型,然后进行运算。强制类型转换自动类型转换publicclassDemo04{publicstaticvoidmain(String[]args){inti=128;doubleb=i;/......
  • Java 多态
    Java多态1.多态方法或对象具有多种形态。是面向对象的三大特征之一,多态是建立在封装、继承基础之上的2.多态的实现:方法的重载中,使用不同的形参调用方法体现出了多态方法的重写中,使用父类或子类的对象调用方法体现出了多态对象的多态:对象的编译类型与运行类型可以不一样......
  • 10道Java基础面试题
    以下是Java基础面试题,相信大家都会有种及眼熟又陌生的感觉、看过可能在短暂的面试后又马上忘记了。JavaPub在这里整理这些容易忘记的重点知识及解答,建议收藏,经常温习查阅。看看这些面试题你会几道@[toc]1.instanceof关键字的作用instanceof是Java的保留关键字。它的作用是测......
  • 交换变量a,b的值(java)
    方法1:引入中间变量inta=10;intb=20;inttemp=a;a=b;b=temp;System.out.println("a="+a+",b="+b);//a=20,b=10方法2:利用赋值符号=inta=10;intb=20;a=b+(a-(b=a));System.out.println("a="+a+&qu......
  • Java之Stream流的常用API
    Java之Stream流的常用APIStream流常见中间方法名称说明Stream<T>filter(Predicate<?superT>predicate)用于对流中的数据进行过滤Stream<T>limit(longmaxSize)获取前几个元素Stream<T>skip(longn)跳过前几个元素Stream<T>distinct()去除流中重复......
  • 《面试1v1》JavaNIO
    我是javapub,一名Markdown程序员从......
  • Java学习-3.流程控制语句
    一、输入和输出println是printline的缩写,表示输出并换行。因此,如果输出后不想换行,可以用print():System.out.print("C.");System.out.println();System.out.println("END");Java还提供了格式化输出的功能。为什么要格式化输出?因为计算机表示的数据不一定适合人来阅读:public......
  • Java学习2
    java学习2继续学习了一些基础的语法内容知识数据类型扩展整数进制标识inta=010; //8八进制0开头intb=0x10; //16十六进制0x开头intc=10; //10十进制0-9A-F浮点数进制标识floatA=1.0;floatB=1.0; 如果进行比较A与B大小是不同的,因为fl......