1. 首页
  2. IT资讯

FastJson稍微使用不当就会导致StackOverflow

“u003Cdivu003Eu003Cpu003E对于广大的开发人员来说,FastJson大家一定都不陌生。u003Cu002Fpu003Eu003Cpu003EFastJson(https:u002Fu002Fgithub.comu002Falibabau002Ffastjson)是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002Fcc434ade66914636bea760d6b4d9822b” img_width=”400″ img_height=”165″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E它具有速度快、使用广泛、测试完备以及使用简单等特点。但是,虽然有这么多优点,但是不代表着就可以随便使用,因为如果使用的方式不正确的话,就可能导致StackOverflowError。而StackOverflowError对于程序来说是无疑是一种灾难。u003Cu002Fpu003Eu003Cpu003E笔者在一次使用FastJson的过程中就遇到了这种情况,后来经过深入源码分析,了解这背后的原理。本文就来从情景再现看是抽丝剥茧,带大家看看坑在哪以及如何避坑。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E问题再现u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EFastJson可以帮助开发在Java Bean和JSON字符串之间互相转换,所以是序列化经常使用的一种方式。u003Cu002Fpu003Eu003Cpu003E有很多时候,我们需要在数据库的某张表中保存一些冗余字段,而这些字段一般会通过JSON字符串的形式保存。比如我们需要在订单表中冗余一些买家的基本信息,如JSON内容:u003Cu002Fpu003Eu003Cpu003E因为这些字段被冗余下来,必定要有地方需要读取这些字段的值。所以,为了方便使用,一般也对定义一个对应的对象。u003Cu002Fpu003Eu003Cpu003E这里推荐一个IDEA插件——JsonFormat,可以一键通过JSON字符串生成一个JavaBean。我们得到以下Bean:u003Cu002Fpu003Eu003Cpu003E然后在代码中,就可以使用FastJson把JSON字符串和Java Bean进行互相转换了。如以下代码:u003Cu002Fpu003Eu003Cpu003E有的时候,如果有多个地方都需要这样互相转换,我们会尝试在BuyerInfo中封装一个方法,专门将对象转换成JSON字符串,如:u003Cu002Fpu003Eu003Cpu003E但是,如果我们定义了这样的方法后,我们再尝试将BuyerInfo转换成JSON字符串的时候就会有问题,如以下测试代码:u003Cu002Fpu003Eu003Cpu003E运行结果:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F19882b08a6094ebda1ffac370e050a2a” img_width=”823″ img_height=”381″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E可以看到,运行以上测试代码后,代码执行时,抛出了StackOverflow。u003Cu002Fpu003Eu003Cpu003E从以上截图中异常的堆栈我们可以看到,主要是在执行到BuyerInfo的getJsonString方法后导致的。u003Cu002Fpu003Eu003Cpu003E那么,为什么会发生这样的问题呢?这就和FastJson的实现原理有关了。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003EFastJson的实现原理u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E关于序列化和反序列化的基础知识大家可以参考u003Ca class=”pgc-link” data-content=”mp” href=”http:u002Fu002Fmp.weixin.qq.comu002Fs?__biz=MzI3NzE0NjcwMg==&mid=2650120836&idx=1&sn=c83a980c0871faf607ae613092c69760&chksm=f36bbfa5c41c36b317c103f27b9d99c26aecba52e4bf614bd73dcadc1e4bc5ab8f99fb082eba&scene=21#wechat_redirect” target=”_blank”u003EJava对象的序列化与反序列化u003Cu002Fau003E,这里不再赘述。u003Cu002Fpu003Eu003Cpu003EFastJson的序列化过程,就是把一个内存中的Java Bean转换成JSON字符串,得到字符串之后就可以通过数据库等方式进行持久化了。u003Cu002Fpu003Eu003Cpu003E那么,FastJson是如何把一个Java Bean转换成字符串的呢,一个Java Bean中有很多属性和方法,哪些属性要保留,哪些要剔除呢,到底遵循什么样的原则呢?u003Cu002Fpu003Eu003Cpu003E其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:u003Cu002Fpu003Eu003Cul class=”list-paddingleft-2″u003Eu003Cliu003E1、基于属性。u003Cu002Fliu003Eu003Cliu003E2、基于setteru002Fgetteru003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E关于Java Bean中的getteru002Fsetter方法的定义其实是有明确的规定的,参考JavaBeans(TM) Specificationu003Cu002Fpu003Eu003Cpu003E而我们所常用的JSON序列化框架中,FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json。u003Cbru003Eu003Cu002Fpu003Eu003Cpu003E不同的框架进行不同的选择是有着不同的思考的,这个大家如果感兴趣,后续文字可以专门介绍下。u003Cu002Fpu003Eu003Cpu003E那么,我们接下来深入一下源码,验证下到底是不是这么回事。u003Cu002Fpu003Eu003Cpu003E分析问题的时候,最好的办法就是沿着异常的堆栈信息,一点点看下去。我们再来回头看看之前异常的堆栈:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F19882b08a6094ebda1ffac370e050a2a” img_width=”823″ img_height=”381″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E我们简化下,可以得到以下调用链:u003Cu002Fpu003Eu003Cpu003E是因为在FastJson将Java对象转换成字符串的时候,出现了死循环,所以导致了StackOverflowError。u003Cu002Fpu003Eu003Cpu003E调用链中的ASMSerializer_1_BuyerInfo,其实是FastJson利用ASM为BuyerInfo生成的一个Serializer,而这个Serializer本质上还是FastJson中内置的JavaBeanSerizlier。u003Cu002Fpu003Eu003Cpu003E读者可以自己试验一下,比如通过如下方式进行degbug,就可以发现ASMSerializer_1_BuyerInfo其实就是JavaBeanSerizlier。u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F0034e27d0025446cacac160abbf4f447″ img_width=”1080″ img_height=”220″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E之所以使用ASM技术,主要是FastJson想通过动态生成类来避免重复执行时的反射开销。但是,在FastJson中,两种序列化实现是并存的,并不是所有情况都需要通过ASM生成一个动态类。读者可以尝试将BuyerInfo作为一个内部类,重新运行以上Demo,再看异常堆栈,就会发现JavaBeanSerizlier的身影。u003Cu002Fpu003Eu003Cpu003E那么,既然是因为出现了循环调用导致了StackOverflowError,我们接下来就将重点放在为什么会出现循环调用上。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003EJavaBeanSerizlier序列化原理u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E我们已经知道,在FastJson序列化的过程中,会使用JavaBeanSerizlier进行,那么就来看下 JavaBeanSerizlier到底做了什么,他是如何帮助FastJson进行序列化的。u003Cu002Fpu003Eu003Cpu003EFastJson在序列化的过程中,会调用JavaBeanSerizlier的write方法进行,我们看一下这个方法的内容:u003Cu002Fpu003Eu003Cpu003E以上代码,我们省略了大部分代码之后,可以看到逻辑相对简单:就是先获取要序列化的对象的所有getter方法,然后遍历方法进行执行,视图通过getter方法获得对应的属性的值。u003Cu002Fpu003Eu003Cpu003E但是,当调用到我们定义的getJsonString方法的时候,进而会调用到JSON.toJSONString(this),就会再次调用到JavaBeanSerizlier的write。如此往复,形成死循环,进而发生StackOverflowError。u003Cu002Fpu003Eu003Cpu003E所以,如果你定义了一个Java对象,定一个了一个getXXX方法,并且在该方法中调用了JSON.toJSONString方法,那么就会发生StackOverflowError!u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E如何避免StackOverflowErroru003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E通过查看FastJson的源码,我们已经基本定位到问题了,那么如何避免这个问题呢?u003Cu002Fpu003Eu003Cpu003E还是从源码入手,既然JavaBeanSerizlier的write方法会尝试获取对象的所有getter方法,那么我们就来看下他到底是怎么获取getter方法的,到底哪些方法会被他识别为”getter”,然后我们再对症下药。u003Cu002Fpu003Eu003Cpu003E在JavaBeanSerizlier的write方法中,getters的获取方式如下:u003Cu002Fpu003Eu003Cpu003E可见,无论是this.sortedGetters还是this.getters,都是JavaBeanSerizlier中的属性,那么就继续往上找,看看JavaBeanSerizlier是如何被初始化的。u003Cu002Fpu003Eu003Cpu003E通过调用栈追根溯源,我们可以发现,JavaBeanSerizlier是在SerializeConfig的成员变量serializers中获取到的,那么继续深入,就要看SerializeConfig是如何被初始化的,即BuyerInfo对应的JavaBeanSerizlier是如何被塞进serializers的。u003Cu002Fpu003Eu003Cpu003E通过调用关系,我们发现,SerializeConfig.serializers是通过SerializeConfig.putInternal方法塞值的:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F98f02f2902db4a5aae4a0b9a73b09c70″ img_width=”1080″ img_height=”213″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E而getObjectWriter中有关于putInternal的调用:u003Cu002Fpu003Eu003Cpu003E这里面就到了我们前面提到的JavaBeanSerializer,我们知道createJavaBeanSerializer是如何创建JavaBeanSerializer的,并且如何设置其中的setters的就可以了。u003Cu002Fpu003Eu003Cpu003E重点来了,TypeUtils.buildBeanInfo就是重点,这里面就到了我们要找的内容。u003Cu002Fpu003Eu003Cpu003EbuildBeanInfo调用了 computeGetters,深入这个方法,看一下setters是如何识别出来的。部分代码如下:u003Cu002Fpu003Eu003Cpu003E这个方法很长很长,以上只是截取了其中的一部分,以上只是做了个简单的判断,判断方法是不是以’get’开头,然后长度是不是小于3,在判断方法名是不是getClass,等等一系列判断。u003Cu002Fpu003Eu003Cpu003E下面我简单画了一张图,列出了其中的核心判断逻辑:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F38b67e7d52ce466d8d3a31b0638c90e0″ img_width=”1080″ img_height=”426″ alt=”FastJson稍微使用不当就会导致StackOverflow” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E那么,通过上图,我们可以看到computeGetters方法在过滤getter方法的时候,是有一定的逻辑的,只要我们想办法利用这些逻辑,就可以避免发生StackOverflowError。u003Cbru003Eu003Cu002Fpu003Eu003Cpu003E这里要提一句,下面将要介绍的几种方法,都是想办法使目标方法不参与序列化的,所以要特别注意下。但是话又说回来,谁会让一个JavaBean的toJSONString进行序列化呢?u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E1、修改方法名u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E首先我们可以通过修改方法名的方式解决这个问题,我们把getJsonString方法的名字改一下,只要不以get开头就可以了,如改为toJsonString。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E2、使用JSONField注解u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E除了修改方法名以外,FastJson还提供了两个注解可以让我们使用,首先介绍JSONField注解,这个注解可以作用在方法上,如果其参数serialize设置成false,那么这个方法就不会被识别为getter方法,就不会参加序列化。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E3、使用JSONType注解u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EFastJson还提供了另外一个注解——JSONType,这个注解用于修饰类,可以指定ignores和includes。如下面的例子,如果使用@JSONType(ignores = “jsonString”)定义BuyerInfo,则也可避免StackOverflowError。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E总结u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EFastJson是使用非常广泛的序列化框架,可以在JSON字符串和Java Bean之间进行互相转换。u003Cu002Fpu003Eu003Cpu003E但是在使用时要尤其注意,不要在Java Bean的getXXX方法中调用JSON.toJSONString方法,否则会导致StackOverflowError。u003Cu002Fpu003Eu003Cpu003E原因是因为FastJson在序列化的时候,会根据一系列规则获取一个对象中的所有getter方法,然后依次执行。u003Cu002Fpu003Eu003Cpu003E如果一定要定义一个方法,调用JSON.toJSONString的话,想要避免这个问题,可以采用以下方法:u003Cu002Fpu003Eu003Cul class=”list-paddingleft-2″u003Eu003Cliu003E1、方法名不以get开头u003Cu002Fliu003Eu003Cliu003E2、使用@JSONField(serialize = false)修饰目标方法u003Cu002Fliu003Eu003Cliu003E3、使用@JSONType修饰该Bean,并ignore掉方法对应的属性名(getXxx -> xxx)u003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E最后,作者之所以写这篇文章,是因为在工作中真的实实在在的碰到了这个问题。u003Cu002Fpu003Eu003Cpu003E发生问题的时候,我立刻想到改个方法名,把getJsonString改成了toJsonString解决了这个问题。因为我之前看到过关于FastJson的简单原理。u003Cu002Fpu003Eu003Cpu003E后来想着,既然FastJson设计成通过getter来进行序列化,那么他一定提供了一个口子,让开发者可以指定某些以get开头的方法不参与序列化。u003Cu002Fpu003Eu003Cpu003E第一时间想到一般这种口子都是通过注解来实现的,于是打开FastJson的源代码,找到了对应的注解。u003Cu002Fpu003Eu003Cpu003E然后,趁着周末的时间,好好的翻了一下FastJson的源代码,彻底弄清楚了其底层的真正原理。u003Cu002Fpu003Eu003Cpu003E以上就是我 发现问题——>分析问题——>解决问题——>问题的升华 的全过程,希望对你有帮助。u003Cu002Fpu003Eu003Cpu003E通过这件事,笔者悟出了一个道理:u003Cu002Fpu003Eu003Cpu003E看过了太多的开发规范,却依然还是会写BUG!u003Cu002Fpu003Eu003Cpu003E希望通过这样一篇小文章,可以让你对这个问题有个基本的印象。u003Cu002Fpu003Eu003Cu002Fdivu003E”

原文始发于:FastJson稍微使用不当就会导致StackOverflow

主题测试文章,只做测试使用。发布者:逗乐男神i,转转请注明出处:http://www.cxybcw.com/26442.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code