衡水招聘:FastJson反序列化和组织函数之间的一点小秘密

admin 3个月前 (04-12) 科技 7 0

列位看官人人下昼好,FastJson想必人人都很熟悉了,很常见的Json序列化工具。今天在下要和人人分享一波FastJson反序列化和组织函数之间的一点小秘密。

 

 

 


下面先进入人人都深恶痛绝的做题环节。哈哈哈...

/**
 * @建立人: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);
    }

人人看看会打印出什么?

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='高三三班'}

没整明了吧,脑壳又嗡嗡的吧!

下面宣布谜底:A!

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

人人都知道FastJson反序列化的时刻,通俗类(不包括接口和抽象类)是通过反射获取组织函数来天生工具的,最后通过反射挪用set方式来设置属性的。

那为什么上面会发生这样新鲜的效果呢。想必有些伶俐的看官已经猜到了吧,对没错,就是组织函数的问题。通常人人在工作中写的类都是这个样子:

@Setter
@Getter
public class User {

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

写好类和属性以后,通过lombok天生get、set方式。组织函数在编译时代由编译器自动天生的一个无参组织函数。在FastJson反序列化的时刻这样的类没有任何问题。

会遵照列位看官的意思反序列化成列位所需的工具。然则,哈哈哈...只要泛起转折词那下面就是重点了。

然则当我们手动为类添加有参组织函数时刻,在编译器编译时,就不会再为其添加无参组织函数,也就是说你的类有且只有你添加的这个组织函数。那这样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的信息:

衡水招聘:FastJson反序列化和组织函数之间的一点小秘密 第1张

 

 

从调试状态的信息可以看到默认组织函数是无参组织函数,默认组织函数的参数长度为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 + '\'' +
                '}';
    }
}

衡水招聘:FastJson反序列化和组织函数之间的一点小秘密 第2张

 

 

从调试截图中可以清晰看到,在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;
            }

到这里有参组织函数的流程基本也就竣事了。

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

衡水招聘:FastJson反序列化和组织函数之间的一点小秘密 第3张

 

 

 在最后稀奇先容下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也是可以序列化和反序列化的。

衡水招聘:FastJson反序列化和组织函数之间的一点小秘密 第4张

 

 

到这里本篇分享就要竣事了,不知道列位看官大大是否满足。满足的话希望给个小小的赞以示激励,谢谢人人的收看。

 

,

sunbet

www.0577meeting.com提供官方APP下载,游戏火爆,口碑极好,服务一流,一直是sunbet会员的首选。

AllBetGaming声明:该文看法仅代表作者自己,与本平台无关。转载请注明:衡水招聘:FastJson反序列化和组织函数之间的一点小秘密

网友评论

  • (*)

最新评论

站点信息

  • 文章总数:594
  • 页面总数:0
  • 分类总数:8
  • 标签总数:928
  • 评论总数:180
  • 浏览总数:4659