推荐阅读:

  1. 学会这些微服务+Tomcat+NGINX+MySQL+Redis,再去面试阿里P7岗吧
  2. “火爆”的微服务架构你还不会?从基础到原理的PDF文档快来学!
  3. 阿里架构师精选Spring Cloud+JVM+MySQL+分布式缓存PDF文档分享

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 = "{" +                ""name":"张三"," +                ""id":"20200411001"," +                ""student":"高三三班"" +                "}";        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@Getterpublic 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的信息:

从调试状态的信息可以看到默认构造函数是无参构造函数,默认构造函数的参数长度为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 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 {    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也是可以序列化和反序列化的。

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

dual mysql 获取序列_FASTJSON反序列化和构造函数之间的一点小秘密相关推荐

  1. java反序列化 构造函数_FastJson反序列化和构造函数之间的一点小秘密

    各位看官大家下午好,FastJson想必大家都很熟悉了,很常见的Json序列化工具.今天在下要和大家分享一波FastJson反序列化和构造函数之间的一点小秘密. 下面先进入大家都深恶痛绝的做题环节.哈 ...

  2. dual mysql 获取序列_MySQL JDBC客户端反序列化漏洞

    标题: MySQL JDBC客户端反序列化漏洞 ☆ 背景介绍☆ 学习思路☆ 搭建测试环境☆ 恶意MySQL插件 1) 获取MySQL 5.7.28源码 2) 在rewrite_example基础上修改 ...

  3. dual mysql 获取序列_MySQL获取周、月、天日期,生成排序号

    常用MySQL生成时间序列 --生成最近七天的日期,不包括当天 SELECT @cdate := date_add(@cdate, interval - 1 day) as date FROM(SEL ...

  4. dual mysql 获取序列_如何使用mysql 一次查询多个序列

    postgresql使用的sql语句如下:selectt.seqValuefrom(selectnextval('seq_attr_id')seqValue,generate_series(1,5)s ...

  5. MYSQL 获取当前日期及日期格式以及非空处理

    2019独角兽企业重金招聘Python工程师标准>>> MYSQL 获取当前日期及日期格式    获取系统日期:  NOW()    格式化日期:    DATE_FORMAT(da ...

  6. MySQL数据库序列的作用_MySQL数据库:序列使用

    本文主要向大家介绍了MySQL数据库的序列使用,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助. MySQL序列是一组整数:1, 2, 3, ...,由于一张数据表只能有一个字段自增 ...

  7. oracle获取序列跳号,Oracle sequence跳号知多少

    Sequence是oracle中的一个非常常用的功能,开发经常会频繁使用.但是在生产环境中经常有应用反馈通过sequence生成的自增主键会出现不连续跳号的现象,而且是几十个几十个地跳,为了弄清楚se ...

  8. mysql 获取天数_MySQL获取某月份的天数

    1.last_day(curdate());获取当月最后一天. 2.DAYOFMONTH(last_day(curdate())); 返回date对应的该月日期.当然这就是当月的天数. 这就出来当月天 ...

  9. mysql数据库序列作用_MySQL 序列使用

    MySQL 序列是一组整数:1, 2, 3, ...,由于一张数据表只能有一个字段自增主键, 如果你想实现其他字段也实现自动增加,就可以使用MySQL序列来实现. 本章我们将介绍如何使用MySQL的序 ...

  10. MySQL系列——MySQL实现序列(Sequence)效果

    MySQL实现序列效果 一般使用序列(Sequence)来处理主键字段,在MySQL中是没有序列的,但是MySQL有提供了自增长(increment)来实现类似的目的,但也只是自增,而不能设置步长.开 ...

最新文章

  1. Windows PE导出表编程2(重组导出表函数地址)
  2. 18亿用户、10万条电源线、4200万月活......创业者的底限究竟在哪里?
  3. python编写安全工具_Python3学习系列(四):编写属于自己的邮件伪造工具
  4. 《设计模式之禅》学习笔记(五)
  5. win7怎么把计算机删除文件,win7不小心删除文件如何恢复_win7删除文件的四种恢复技巧...
  6. 紫外线测光仪的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  7. 安卓一键清理内存_雨点清理app下载-雨点清理手机版v1.0
  8. 动态规划算法实现0/1背包问题
  9. Android USB Tethering的实现以及代码流程
  10. Linux服务器之Samba匿名访问配置
  11. js获取本月初与月底的时间、获取前一天的时间。
  12. byref和byval什么时候该使用什么时候不该使用
  13. 计算机专业硕士论文能编吗,论文发表:计算机硕士论文编数据被发现怎么办?.docx...
  14. 专利申请怎样做快速预审?
  15. 经典排序算法-----归并排序(C语言实现)
  16. js map基本操作和循环取值
  17. 无机光致变色化合物-过渡金属氧化物WO3/MoO3/TiO2
  18. JavaScript学习笔记10
  19. C语言入门(什么是C语言,C语言的编程机制以及一些基础计算机概念)
  20. 观察者模式——应聘者和应聘公司的关系

热门文章

  1. 算法-无向图(深度优先搜索和广度优先搜索)
  2. 虚拟机常见的几种上网方式
  3. ospf-3型和5型汇总
  4. 前端跨域 ——实践总结,亲测有效
  5. sendTemplateMessage微信小程序消息推送 前段 + 后端(thinkphp3.2)
  6. 网站关键词编写方法,注意事项。
  7. matlab中emd未定义,EMD的Matlab程序
  8. js Promise理解,同时请求多个接口等
  9. idea中改了jsp代码不生效_使用IDEA编写jsp时EL表达式不起作用的问题及解决方法...
  10. python生产和消费模型_【Python】python 生产/消费模型