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

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

/**

* @创建人:Raiden

* @Descriotion:

* @Date:Created in 15:53 2020/3/21

* @Modified By:*/

public classUser {privateString name;privateString id;privateString student;publicUser(String name,String id){this.name =name;this.id =id;

}publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}publicString getId() {returnid;

}public voidsetId(String id) {this.id =id;

}publicString getStudent() {returnstudent;

}public voidsetStudent(String student) {this.student =student;

}

@OverridepublicString toString() {return "User{" +

"name='" + name + '\'' +

", id='" + id + '\'' +

", student='" + student + '\'' +

'}';

}

}

@Testpublic void testFastJosn() throwsThrowable {

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

@Getterpublic classUser {privateString name;privateString id;privateString student;

}

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

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

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

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

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

我们先看看他的属性:

public classJavaBeanInfo {public final Class>clazz;public final Class>builderClass;//默认的构造方法放在这

public final Constructor>defaultConstructor;//手动写的构造方法放在这

public final Constructor>creatorConstructor;public finalMethod factoryMethod;public finalMethod buildMethod;public final intdefaultConstructorParameterSize;public finalFieldInfo[] fields;public finalFieldInfo[] sortedFields;public final intparserFeatures;public finalJSONType jsonType;public finalString typeName;public finalString typeKey;publicString[] orders;publicType[] creatorConstructorParameterTypes;publicString[] creatorConstructorParameters;public booleankotlin;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;

}

}

}

}returndefaultConstructor;

}

接下来使用无参构造函数进行反序列化,从调试状态我们可以看到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串中的属性名称对应不上将无法赋值,这里一定要记牢,否则会出现莫名其妙的问题。举个例子:

publicUser(String a,String i,String s){this.name =a;this.id =i;this.student =s;

}

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

当然这里也可以通过JSONField来从定义参数名称。想详细了解的同学可以去看看ASMUtils.lookupParameterNames(constructor)这个方法的源码。因为篇幅问题在这就不在赘述。

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

public classUser {privateString name;privateString id;privateString student;publicUser(String name,String id){this.name =name;this.id =id;

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

}publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}publicString getId() {returnid;

}public voidsetId(String id) {this.id =id;

}publicString getStudent() {returnstudent;

}public voidsetStudent(String student) {this.student =student;

}

@OverridepublicString 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

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

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.Entryentry : 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) throwsIllegalAccessException, InvocationTargetException {return method != null

?method.invoke(javaObject)

: field.get(javaObject);

}public void set(Object javaObject, Object value) throwsIllegalAccessException, InvocationTargetException {if (method != null) {

method.invoke(javaObject,newObject[] { value });return;

}

field.set(javaObject, value);

}

}

从源代码片段中可以看出,不管是赋值还是获取值,都是先通过反射调用get,set方法来实现的,但是如果没有get,set方法会通过反射调用field来实现。也就是说没有get,set也是可以序列化和反序列化的。

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

java反序列化 构造函数_FastJson反序列化和构造函数之间的一点小秘密相关推荐

  1. Java 中序列化与反序列化

    一. 序列化和反序列化概念 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.将程序中的对象,放入文 ...

  2. java中序列化与反序列化_Java中的序列化

    java中序列化与反序列化 Java提供了一种称为序列化的机制,以按字节的有序或字节序列的形式持久化Java对象,其中包括对象的数据以及有关对象的类型和存储在对象中的数据类型的信息. 因此,如果我们已 ...

  3. Java IO 序列化与反序列化

    Java IO 序列化与反序列化 Java中的序列化与反序列化 序列化定义 将对象转换为字节流保存起来,并在以后还原这个对象,这种机制叫做对象序列化. 将一个对象保存到永久存储设备上称为持久化. 一个 ...

  4. Java 的序列化和反序列化,你该知道得更多

    作者 l 会点代码的大叔(CodeDaShu) Java 在内存中创建可以复用的对象,这些对象的生命周期不会比 JVM 的生命周期更长,如果有一些对象需要在 JVM 停止后保存(硬盘),并在 JVM ...

  5. 深入分析Java的序列化与反序列化

    阅读目录 Java对象的序列化 如何对Java对象进行序列化与反序列化 序列化及反序列化相关知识 ArrayList的序列化 ObjectOutputStream 总结 序列化是一种对象持久化的手段. ...

  6. Java性能优化(3):通过私有构造函数强化不可实例化的能力

    有时候你可能会编写出只包含静态方法和静态域的类,这样的类有一些很不好的名声,因为有些人在面向对象的语言中滥用这样的类来编写过程化的程序.尽管如此,它们也确实有它们特有的用处,我们可以利用这种类,把操作 ...

  7. 代码即财富之我学Java对象序列化与反序列化(2)

    2019独角兽企业重金招聘Python工程师标准>>> 我们在程序创建的Java对象都是存在于JVM内存中的,也就是Java对象的生命周期一定不会长于JVM,所以如何以一种持久化的方 ...

  8. Java反射机制——获取成员变量构造函数

    2019独角兽企业重金招聘Python工程师标准>>> Java反射机制--获取成员变量&构造函数 一.成员变量是java.lang.reflect.Field的对象 1.F ...

  9. 四十四、深入Java 的序列化和反序列化

    @Author:Runsen @Date:2020/6/8 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

最新文章

  1. c++ 中const的使用
  2. 2015-07-22 JQuery 第二课(JQ元素获取,添加,删除,判断,遍历,取值,样式设置,改变对象,切换)...
  3. 管道实现进程间通讯 、WaitNamedPipe
  4. uniapp中实现每次点击左侧菜单右边区域都从顶部开始
  5. 数学--数论--HDU-2698 Maximum Multiple(规律)
  6. 好文章系列(都是网上非常好的文章)
  7. java两种绑定方式_Javascript绑定事件的两种方式的区别
  8. LeetCode 1374. 生成每种字符都是奇数个的字符串
  9. Python基础(四)--字典与集合
  10. matlab画图显示中文
  11. 七个你可能不了解的CSS单位
  12. Excel中快速调整行高或列宽为适合内容显示的长度
  13. linux内核模块签名,linux内核模块签名
  14. C#制作QQ截图的自动框选功能的个人思路(三)自动框选
  15. matlab 脚本文件 函数,Matlab 脚本文件script和函数文件function的区别
  16. MIMO-OTFS in High-Doppler Fading Channels:Signal Detection and Channel Estimation(5)
  17. ll1语法分析程序c语言,c语言语法分析器,实现ll1分析
  18. 矩阵的谱分解 (详细推导步骤~~~特征值分解特征向量
  19. 弱监督学习-snorkel
  20. 提示https不安全的原因及解决办法

热门文章

  1. 表的插入、更新、删除、合并操作_5_通过其它表插入
  2. linux内核申请内存的方法,Linux内核空间的内存申请常用函数
  3. python3源代码_Python3源代码编译安装
  4. ImportError: /home/kzl/anaconda2/bin/../lib/libstdc++.so.6: version `GLIBCXX_3.4.21' not found
  5. 装箱---一个工厂制造的产品形状都是长方体,它们的高度都是 h,长和宽都相等,一共有六个型号,他们的长宽分别为 1*1, 2*2, 3*3, 4*4, 5*5, 6*6.
  6. 解读阿里云oss-android/ios-sdk 断点续传(多线程)
  7. 阿里云Redis (安装包安装篇)
  8. 《Ruby程序员修炼之道》(第2版)目录—导读
  9. [实用]DNS解析命令,静静地学会【转载】
  10. Tasker文件夹说明