JSON.toJSONString使用異常分析
先說說坑
JSON.toString在序列化對象時,默認通過的是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)//關鍵點
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不存在
關于延遲加載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); }
這邊存在兩個迷惑性行為
- 啟動的時候怎么調用了getAllXX方法
- 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)//關鍵點
at com.alibaba.dubbo.config.ServiceConfig.doExport(ServiceConfig.java:321)
at com.alibaba.dubbo.config.ServiceConfig.export(ServiceConfig.java:218)
可以復盤出問題發(fā)生的現(xiàn)場
dubbo服務進行export的時候調用了ExportedInterfaceManager.addInterface方法,而在addInterface方法中調用的JSON.toJSONString方法觸發(fā)了ProductServiceQueryServiceImpl.getAllProductService方法
在看了ExportedInterfaceManager.addInterface源碼之后,問題的起因浮出水面
ExportedInterfaceManager這個類是用來針對接口暴露http服務時收集元數(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);
這段代碼會獲取代理的原始對象,導致事務失效。
問題危害
拋開獲取原始對象這個邏輯不說,這個bug的致命之處在于,他會調用所暴露dubbo接口中所有get*()
格式的方法
問題解決
解決方式很簡單,有以下兩種
- 不要序列化ref(加fastjson注解或字段加transient)
- 去掉打印日志邏輯
在反饋這個問題后,中間件團隊的改動如下
以上就是JSON.toJSONString使用異常分析的詳細內容,更多關于JSON.toJSONString異常的資料請關注腳本之家其它相關文章!
相關文章
Java 中的 BufferedReader 介紹_動力節(jié)點Java學院整理
BufferedReader 是緩沖字符輸入流。它繼承于Reader。接下來通過本文給大家介紹BufferedReader的相關知識,需要的朋友參考下吧2017-05-05