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"} 复制代码
最近在迭代一个老项目,升级中间件框架版本(不升级不给打包部署)后,在项目 启动 的时候居然抛出以下异常
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会触发延迟加载 return XXConverter.INSTANCE.entityListToDTOList(result); } 复制代码
这边存在两个迷惑性行为
精简上面的异常栈
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) 复制代码
可以复盘出问题发生的现场
dubbo服务进行export的时候调用了ExportedInterfaceManager.addInterface方法,而在addInterface方法中调用的JSON.toJSONString方法触发了ProductServiceQueryServiceImpl.getAllProductService方法
在看了ExportedInterfaceManager.addInterface源码之后,问题的起因浮出水面
ExportedInterfaceManager这个类是用来针对接口暴露http服务时收集元数据使用
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触发了ProductServiceQueryServiceImpl中的get方法
而对于第二个问题, obj = getObjectTarget(obj);
这段代码会获取代理的原始对象,导致事务失效。
抛开获取原始对象这个逻辑不说,这个bug的致命之处在于,他会调用所暴露dubbo接口中所有 get*()
格式的方法