Знаете ли вы яму FastJson?

Java

Поговорим о яме

Когда JSON.toString сериализует объект, он использует метод get*() для поиска свойств по умолчанию, а не определенного свойства, и игнорирует свойства временных аннотаций.

Тестовый случай выглядит следующим образом

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:Сессия не существует при ленивой загрузке

Что касается проблемы ленивой загрузки без сеанса, вы можете увидетьКак решить ленивую загрузку JPA без ошибки сеанса

Метод от поиска журнала до создания исключения

@Override
@Transactional(rollbackFor = Exception.class)
public List<XXDTO> getAllXX() {
    List<XXEntity> result = xXQueryRepository.findAll();
    //下面的converter会触发延迟加载
    return XXConverter.INSTANCE.entityListToDTOList(result);
}

Здесь есть два обманчивых поведения

  1. Как вызвать метод getAllXX при запуске
  2. Я добавил @Transactional в getAllXX, теоретически есть сессия

Устранение неполадок

Упростите стек исключений выше

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 вызывается метод ExportedInterfaceManager.addInterface, а метод JSON.toJSONString, вызываемый в методе addInterface, запускает метод 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 указывает на ProductServiceQueryServiceImpl. При печати журнала JSON.toJSONString запускает метод get в ProductServiceQueryServiceImpl.

А по второму вопросу,obj = getObjectTarget(obj);Этот код захватывает исходный объект прокси, делая транзакцию недействительной.

проблемы опасности

Помимо логики получения исходного объекта, фатальной особенностью этой ошибки является то, что она будет вызывать все открытые интерфейсы dubbo.get*()метод форматирования

задача решена

Решение очень простое, есть следующие два

  1. Не сериализовать ref (добавьте аннотацию fastjson или поле с переходным процессом)
  2. Удалить логику журнала печати

После отзывов по этой проблеме изменения, внесенные командой промежуточного программного обеспечения, заключаются в следующем.