美文网首页
JSON.toJSONString的一个坑

JSON.toJSONString的一个坑

作者: 土豆肉丝盖浇饭 | 来源:发表于2020-04-27 13:09 被阅读0次

    先说说坑

    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);
    }
    

    这边存在两个迷惑性行为

    1. 启动的时候怎么调用了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)//关键点
        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*()格式的方法

    问题解决

    解决方式很简单,有以下两种

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

    在反馈这个问题后,中间件团队的改动如下

    相关文章

      网友评论

          本文标题:JSON.toJSONString的一个坑

          本文链接:https://www.haomeiwen.com/subject/jkfywhtx.html