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

博客园 · · 272 次点击 · · 开始浏览    

最近在使用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方法时,如果没有对应的字段会报错的问题有待研究。

272 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传