FastJson反序列化和构造函数之间的一点小秘密

各位看官你们下午好,FastJson想必你们都很熟悉了,很常见的Json序列化工具。今天在下要和你们分享一波FastJson反序列化和构造函数之间的一点小秘密。java

 

 

 


下面先进入你们都深恶痛绝的作题环节。哈哈哈...web

/**
 * @建立人:Raiden
 * @Descriotion:
 * @Date:Created in 15:53 2020/3/21
 * @Modified By:
 */
public class User {
    private String name;
    private String id;
    private String student;
    
    public User(String name,String id){
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getStudent() {
        return student;
    }

    public void setStudent(String student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", student='" + student + '\'' +
                '}';
    }
}
    @Test
    public void testFastJosn() throws Throwable {
        String userStr = "{\n" +
                "\t\"name\":\"张三\",\n" +
                "\t\"id\":\"20200411001\",\n" +
                "\t\"student\":\"高三三班\"\n" +
                "}";
        User user = JSON.parseObject(userStr, User.class);
        System.err.println(user);
    }

你们看看会打印出什么?spring

A:User{name='张三', id='20200411001', student='null'}
B:User{name='张三', id='20200411001', student='高三三班'}
C:User{name='null', id='20200411001', student='高三三班'}
D:User{name='null', id='null', student='高三三班'}

没整明白吧,脑壳又嗡嗡的吧!json

下面公布答案:A!数组

是否是有点意外啊,下面就由在下为各位解答一下疑惑。ide

你们都知道FastJson反序列化的时候,普通类(不包括接口和抽象类)是经过反射获取构造函数来生成对象的,最后经过反射调用set方法来设置属性的。函数

那为何上面会产生这样奇怪的结果呢。想必有些聪明的看官已经猜到了吧,对没错,就是构造函数的问题。一般你们在工做中写的类都是这个样子:工具

@Setter
@Getter
public class User {

    private String name;
    private String id;
    private String student;
}

写好类和属性之后,经过lombok生成get、set方法。构造函数在编译期间由编译器自动生成的一个无参构造函数。在FastJson反序列化的时候这样的类没有任何问题。ui

会依照各位看官的意思反序列化成各位所需的对象。可是,哈哈哈...只要出现转折词那下面就是重点了。this

可是当咱们手动为类添加有参构造函数时候,在编译器编译时,就不会再为其添加无参构造函数,也就是说你的类有且只有你添加的这个构造函数。那这样FastJson是如何反序列化生成实例的呢?

下面请各位看官移步到FastJson反序列化方法调用图和源码片断:

 

 

 咱们此次要讲的重点在JavaBeanInfo的build方法中。从类名中大奖能够知道,这是FastJson内部存储反序列化对象信息的类。这其中就包含了建立该类的构造方法信息。

咱们先看看他的属性:

public class JavaBeanInfo {

    public final Class<?> clazz;
    public final Class<?> builderClass;
    //默认的构造方法放在这
    public final Constructor<?> defaultConstructor;
    //手动写的构造方法放在这
    public final Constructor<?> creatorConstructor;
    public final Method factoryMethod;
    public final Method buildMethod;

    public final int defaultConstructorParameterSize;

    public final FieldInfo[] fields;
    public final FieldInfo[] sortedFields;

    public final int parserFeatures;

    public final JSONType jsonType;

    public final String typeName;
    public final String typeKey;

    public String[] orders;

    public Type[] creatorConstructorParameterTypes;
    public String[] creatorConstructorParameters;

    public boolean kotlin;
    public Constructor<?> kotlinDefaultConstructor;

在其中咱们会发现 defaultConstructor 和 creatorConstructor 两个属性。从属性名称各位看官应该能看出来其中defaultConstructor 是默认的构造函数,那咱们来看看获取他的源码片断:

这段代码的含义是先遍历全部的构造函数,看看其中是否存在无参构造函数,若是存在直接返回。

    static Constructor<?> getDefaultConstructor(Class<?> clazz, final Constructor<?>[] constructors) {
        if (Modifier.isAbstract(clazz.getModifiers())) {
            return null;
        }

        Constructor<?> defaultConstructor = null;
        //这里很好理解 先遍历下全部的构造函数,找到其中无参构造函数
        for (Constructor<?> constructor : constructors) {
            if (constructor.getParameterTypes().length == 0) {
                defaultConstructor = constructor;
                break;
            }
        }

        if (defaultConstructor == null) {
            if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
                Class<?>[] types;
                for (Constructor<?> constructor : constructors) {
                    if ((types = constructor.getParameterTypes()).length == 1
                            && types[0].equals(clazz.getDeclaringClass())) {
                        defaultConstructor = constructor;
                        break;
                    }
                }
            }
        }

        return defaultConstructor;
    }

接下来使用无参构造函数进行反序列化,从调试状态咱们能够看到JavaBeanInfo的信息:

 

 

从调试状态的信息能够看到默认构造函数是无参构造函数,默认构造函数的参数长度为0个。

接下了请各位看官了解一下有参构造函数的获取方式:(下面的代码取自JavaBeanInfo 的403行到455行)

                    for (Constructor constructor : constructors) {
                        Class<?>[] parameterTypes = constructor.getParameterTypes();

                        if (className.equals("org.springframework.security.web.authentication.WebAuthenticationDetails")) {
                            if (parameterTypes.length == 2 && parameterTypes[0] == String.class && parameterTypes[1] == String.class) {
                                creatorConstructor = constructor;
                                creatorConstructor.setAccessible(true);
                                paramNames = ASMUtils.lookupParameterNames(constructor);
                                break;
                            }
                        }

                        if (className.equals("org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken")) {
                            if (parameterTypes.length == 3
                                    && parameterTypes[0] == Object.class
                                    && parameterTypes[1] == Object.class
                                    && parameterTypes[2] == Collection.class) {
                                creatorConstructor = constructor;
                                creatorConstructor.setAccessible(true);
                                paramNames = new String[] {"principal", "credentials", "authorities"};
                                break;
                            }
                        }

                        if (className.equals("org.springframework.security.core.authority.SimpleGrantedAuthority")) {
                            if (parameterTypes.length == 1
                                    && parameterTypes[0] == String.class) {
                                creatorConstructor = constructor;
                                paramNames = new String[] {"authority"};
                                break;
                            }
                        }

                        boolean is_public = (constructor.getModifiers() & Modifier.PUBLIC) != 0;
                        if (!is_public) {
                            continue;
                        }
                        //前面的方法都是进行一些过滤 下面的才是获取手动有参构造函数的关键。
                        //首先会获取构造函数的参数名称列表 若是参数列表为空或者长度为0 则放弃该方法,开始下一次循环
                        //这里的获取参数名称使用的并非java8中提供的获取方法参数名称的方式
                        //而是经过流读取class文件的的方式来获取的
                        String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor);
                        if (lookupParameterNames == null || lookupParameterNames.length == 0) {
                            continue;
                        }
                        //下面这段方法很显然就是在比较并交换,若是该有参构造函数的参数个数大于以前的构造方法中
                        //参数个数最多的构造方法,则用这个构造方法和参数名称数组替换以前保存的构造方法和参数名称数组
                        if (creatorConstructor != null
                                && paramNames != null && lookupParameterNames.length <= paramNames.length) {
                            continue;
                        }

                        paramNames = lookupParameterNames;
                        creatorConstructor = constructor;
                    }

上面的方法的做用是从全部的构造方法中获取参数最多的一个,并将其放入JaveBeanInfo的creatorConstructor属性中,供后面实例化对象使用。

要特别注意一下,这里的获取参数名称使用的并非java8中提供的获取方法参数名称的方式,而是经过流读取class文件的的方式来获取的。

在赋值的时候,会经过参数名称去json串中查找对应名称的字段来赋值,而且在经过构造函数赋值完毕以后,将再也不经过set方法赋值(这里有坑必定要记住,不然会出现赋值不上的莫名其妙的错误)。

若是构造函数中的入参命名和JSON串中的属性名称对应不上将没法赋值,这里必定要记牢,不然会出现莫名其妙的问题。举个例子:

    public User(String a,String i,String s){
        this.name = a;
        this.id = i;
        this.student = s;
    }

上面所示的构造函数,在Json串中就必须有对应的属性a,i,s。不然会致使反序列化后属性为空。

固然这里也能够经过JSONField来从定义参数名称。想详细了解的同窗能够去看看ASMUtils.lookupParameterNames(constructor)这个方法的源码。由于篇幅问题在这就不在赘述。

下面咱们使用以下类进行调试,看看是否如我所说的。

public class User {


    private String name;
    private String id;
    private String student;


    public User(String name,String id){
        this.name = name;
        this.id = id;
    }

    public User(String name,String id,String student){
        this.name = name;
        this.id = id;
        this.student = student;
    }


    public String getName() {
        return name;
    }

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getStudent() {
        return student;
    }

    public void setStudent(String student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", student='" + student + '\'' +
                '}';
    }
}

 

 

从调试截图中能够清晰看到,在JavaBeanInfo中creatorConstructor属性存放的是有三个参数的构造方法,并且三个参数的类型都是String。这正好印证了咱们上面的结论。

从JavaBeanDeserializer类的969行到1026行源代码片断能够看到,这里直接经过反射调用有参构造函数生成了要反序列化的类。而且由于这里由于JavaBeanInfo中 buildMethod 属性为空,因此在1025行代码处直接返回结果。

至此方法结束,不在进行set赋值。

                if (beanInfo.creatorConstructor != null) {
                    boolean hasNull = false;
                    if (beanInfo.kotlin) {
                        for (int i = 0; i < params.length; i++) {
                            if (params[i] == null && beanInfo.fields != null && i < beanInfo.fields.length) {
                                FieldInfo fieldInfo = beanInfo.fields[i];
                                if (fieldInfo.fieldClass == String.class) {
                                    hasNull = true;
                                }
                                break;
                            }
                        }
                    }

                    try {
                        if (hasNull && beanInfo.kotlinDefaultConstructor != null) {
                            object = beanInfo.kotlinDefaultConstructor.newInstance(new Object[0]);

                            for (int i = 0; i < params.length; i++) {
                                final Object param = params[i];
                                if (param != null && beanInfo.fields != null && i < beanInfo.fields.length) {
                                    FieldInfo fieldInfo = beanInfo.fields[i];
                                    fieldInfo.set(object, param);
                                }
                            }
                        } else {
                            //在这经过反射直接调用有参构造函数 而且输入参数进行初始化
                            object = beanInfo.creatorConstructor.newInstance(params);
                        }
                    } catch (Exception e) {
                        throw new JSONException("create instance error, " + paramNames + ", "
                                                + beanInfo.creatorConstructor.toGenericString(), e);
                    }

                    if (paramNames != null) {
                        for (Map.Entry<String, Object> entry : fieldValues.entrySet()) {
                            FieldDeserializer fieldDeserializer = getFieldDeserializer(entry.getKey());
                            if (fieldDeserializer != null) {
                                fieldDeserializer.setValue(object, entry.getValue());
                            }
                        }
                    }
                } else if (beanInfo.factoryMethod != null) {
                    try {
                        object = beanInfo.factoryMethod.invoke(null, params);
                    } catch (Exception e) {
                        throw new JSONException("create factory method error, " + beanInfo.factoryMethod.toString(), e);
                    }
                }

                if (childContext != null) {
                    childContext.object = object;
                }
            }
            //这里由于JavaBeanInfo中 buildMethod 属性为空,因此直接返回结果方法结束,不在进行set赋值
            Method buildMethod = beanInfo.buildMethod;
            if (buildMethod == null) {
                return (T) object;
            }

到这里有参构造函数的流程基本也就结束了。

下面是反序列化流程的逻辑图:

 

 

 在最后特别介绍下com.alibaba.fastjson.util.FieldInfo 这个类。Fastjson就是经过这个类给无参构造函生成的实例赋值的。

public class FieldInfo implements Comparable<FieldInfo> {

    public Object get(Object javaObject) throws IllegalAccessException, InvocationTargetException {
        return method != null
                ? method.invoke(javaObject)
                : field.get(javaObject);
    }

    public void set(Object javaObject, Object value) throws IllegalAccessException, InvocationTargetException {
        if (method != null) {
            method.invoke(javaObject, new Object[] { value });
            return;
        }

        field.set(javaObject, value);
    }
}

从源代码片断中能够看出,不论是赋值仍是获取值,都是先经过反射调用get,set方法来实现的,可是若是没有get,set方法会经过反射调用field来实现。也就是说没有get,set也是能够序列化和反序列化的。

 

 

到这里本篇分享就要结束了,不知道各位看官大大是否满意。满意的话但愿给个小小的赞以示鼓励,感谢你们的收看。

相关文章
相关标签/搜索