JSON.toJSONString的一個坑

先說說坑

JSON.toString在序列化對象時,默認(rèn)通過的是get*()方法來查找屬性,而不是具體某個屬性,同時回忽略transient注解的屬性丰捷。

測試案例如下

public class FastJsonTest {

    public static void main(String[] args) {
        Person person = new Person();
        person.setBirth(new Date());
        System.out.println(JSON.toJSONString(person));
    }

    public static class Person{

        private Integer age =123;

        private transient Date birth;

        public Date getBirth() {
            return birth;
        }

        public void setBirth(Date birth) {
            this.birth = birth;
        }

        public String getName(){
            return "scj";
        }

    }

}

輸出

{"name":"scj"}

問題發(fā)生

最近在迭代一個老項目坯墨,升級中間件框架版本(不升級不給打包部署)后寂汇,在項目啟動的時候居然拋出以下異常

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.masaike.yama.platform.domain.model.product.ProductServiceEntity.properties, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:582)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:201)
    at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:145)
    at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:261)
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.productServicePropertyVOListToProductServicePropertyDTOList(ProductServiceConverterImpl.java:139)
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.map(ProductServiceConverterImpl.java:50)
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.entityListToDTOList(ProductServiceConverterImpl.java:32)
    at com.masaike.yama.platform.query.ProductServiceQueryServiceImpl.getAllProductService(ProductServiceQueryServiceImpl.java:43)
    at com.alibaba.fastjson.serializer.ASMSerializer_12_ProductServiceQueryServiceImpl.write(Unknown Source)
    at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
    at com.alibaba.fastjson.serializer.ASMSerializer_1_InterfaceInfo.write(Unknown Source)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:285)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:745)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:683)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:648)
    at com.alibaba.dubbo.config.masaikehttp.ExportedInterfaceManager.addInterface(ExportedInterfaceManager.java:104)//關(guān)鍵點
    at com.alibaba.dubbo.config.ServiceConfig.doExport(ServiceConfig.java:321)
    at com.alibaba.dubbo.config.ServiceConfig.export(ServiceConfig.java:218)
    at com.alibaba.dubbo.config.spring.ServiceBean.onApplicationEvent(ServiceBean.java:123)
    at com.alibaba.dubbo.config.spring.ServiceBean.onApplicationEvent(ServiceBean.java:49)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:400)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:354)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:886)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:161)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1242)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1230)
    at com.masaike.yama.bootstrap.BootStrapApplication.main(BootStrapApplication.java:36)

這個一個使用JPA時常見問題:延遲加載的時候session不存在

關(guān)于延遲加載no-session問題,可以看如何解決JPA延遲加載no Session報錯

從日志定位到拋出異常的方法為

@Override
@Transactional(rollbackFor = Exception.class)
public List<XXDTO> getAllXX() {
    List<XXEntity> result = xXQueryRepository.findAll();
    //下面的converter會觸發(fā)延遲加載
    return XXConverter.INSTANCE.entityListToDTOList(result);
}

這邊存在兩個迷惑性行為

  1. 啟動的時候怎么調(diào)用了getAllXX方法
  2. getAllXX我加了@Transactional捣染,理論上是有session的

問題排查

精簡上面的異常棧

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.masaike.yama.platform.domain.model.product.ProductServiceEntity.properties, could not initialize proxy - no Session
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.productServicePropertyVOListToProductServicePropertyDTOList(ProductServiceConverterImpl.java:139)
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.map(ProductServiceConverterImpl.java:50)
    at com.masaike.yama.platform.infrastructure.converter.ProductServiceConverterImpl.entityListToDTOList(ProductServiceConverterImpl.java:32)
    at com.masaike.yama.platform.query.ProductServiceQueryServiceImpl.getAllProductService(ProductServiceQueryServiceImpl.java:43)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:648)
    at com.alibaba.dubbo.config.masaikehttp.ExportedInterfaceManager.addInterface(ExportedInterfaceManager.java:104)//關(guān)鍵點
    at com.alibaba.dubbo.config.ServiceConfig.doExport(ServiceConfig.java:321)
    at com.alibaba.dubbo.config.ServiceConfig.export(ServiceConfig.java:218)

可以復(fù)盤出問題發(fā)生的現(xiàn)場

dubbo服務(wù)進行export的時候調(diào)用了ExportedInterfaceManager.addInterface方法骄瓣,而在addInterface方法中調(diào)用的JSON.toJSONString方法觸發(fā)了ProductServiceQueryServiceImpl.getAllProductService方法

在看了ExportedInterfaceManager.addInterface源碼之后,問題的起因浮出水面

ExportedInterfaceManager這個類是用來針對接口暴露http服務(wù)時收集元數(shù)據(jù)使用

public synchronized void addInterface(Class<?> interfaceCls, Object obj) {
    //如果是代理類獲取代理類對象
    obj = getObjectTarget(obj);//獲取原始對象
    
    //...

    InterfaceInfo interfaceInfo = new InterfaceInfo();
    interfaceInfo.setInterfaceName(interfaceName);
    interfaceInfo.setRef(obj);//致命之處

    //...

    logger.info(String.format("start to addInterface into interfaceMap,interfaceName[%s],interfaceInfo[%s]",interfaceName, JSON.toJSONString(interfaceInfo)));//致命之處

    // add interface info to map
    interfaceMap.put(interfaceName, interfaceInfo);

}

對于第一個問題耍攘,InterfaceInfo的ref指向ProductServiceQueryServiceImpl榕栏,在打印日志的時候,JSON.toJSONString觸發(fā)了ProductServiceQueryServiceImpl中的get方法

而對于第二個問題蕾各,obj = getObjectTarget(obj);這段代碼會獲取代理的原始對象扒磁,導(dǎo)致事務(wù)失效。

問題危害

拋開獲取原始對象這個邏輯不說式曲,這個bug的致命之處在于妨托,他會調(diào)用所暴露dubbo接口中所有get*()格式的方法

問題解決

解決方式很簡單,有以下兩種

  1. 不要序列化ref(加fastjson注解或字段加transient)
  2. 去掉打印日志邏輯

在反饋這個問題后吝羞,中間件團隊的改動如下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兰伤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钧排,更是在濱河造成了極大的恐慌敦腔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恨溜,死亡現(xiàn)場離奇詭異符衔,居然都是意外死亡,警方通過查閱死者的電腦和手機糟袁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門判族,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人系吭,你說我怎么就攤上這事五嫂。” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵沃缘,是天一觀的道長躯枢。 經(jīng)常有香客問我,道長槐臀,這世上最難降的妖魔是什么锄蹂? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮水慨,結(jié)果婚禮上得糜,老公的妹妹穿的比我還像新娘。我一直安慰自己晰洒,他們只是感情好朝抖,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谍珊,像睡著了一般治宣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上砌滞,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天侮邀,我揣著相機與錄音,去河邊找鬼贝润。 笑死绊茧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的打掘。 我是一名探鬼主播华畏,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胧卤!你這毒婦竟也來了唯绍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤枝誊,失蹤者是張志新(化名)和其女友劉穎况芒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叶撒,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡绝骚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祠够。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片压汪。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖古瓤,靈堂內(nèi)的尸體忽然破棺而出止剖,到底是詐尸還是另有隱情腺阳,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布穿香,位于F島的核電站亭引,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏皮获。R本人自食惡果不足惜焙蚓,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洒宝。 院中可真熱鬧购公,春花似錦、人聲如沸雁歌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽将宪。三九已至绘闷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間较坛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工扒最, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丑勤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓吧趣,卻偏偏與公主長得像法竞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子强挫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容