记录一个gson相关的问题排查

问题:生产环境多个实例代码一模一样,并且启动时间也一样(都是4天前),其中有一个实例报:java.lang.IllegalArgumentException: Can not set java.lang.String field xxx.ProductSpecData.id to xxx.ProductSpecEntity.值得注意的是,当请求其他实例的这个接口时没有任何问题,就只有其中一台实例报这个问题。页面的表现就是:时而查询成功,时而报错。

堆栈信息如下:
java.lang.IllegalArgumentException: Can not set java.lang.String field xxx.ProductSpecData.id to xxx.ProductSpecEntity
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at
java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
at
java.base/java.lang.reflect.Field.get(Field.java:418)
at com. doodle
.gson.internal.bind.ReflectiveTypeAdapterFactory 1. w r i t e F i e l d ( R e f l e c t i v e T y p e A d a p t e r F a c t o r y . j a v a : 136 ) a t c o m . g o o g l e . g s o n . i n t e r n a l . b i n d . R e f l e c t i v e T y p e A d a p t e r F a c t o r y S A d a p t e r . w r i t e ( R e f l e c t i v e T y p e A d a p t e r F a c t o r y . j a v a : 241 ) a t c o m . g o o g l e . g s o n . G s o n 1.writeField(ReflectiveTypeAdapterFactory.java:136) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactorySAdapter.write(ReflectiveTypeAdapterFactory.java:241) at com.google.gson.Gson 1.writeField(ReflectiveTypeAdapterFactory.java:136)atcom.google.gson.internal.bind.ReflectiveTypeAdapterFactorySAdapter.write(ReflectiveTypeAdapterFactory.java:241)atcom.google.gson.GsonFutureTypeAdapter.write(Gson.java:976)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeW/rapper.java:69)
at com.google.gson.internal.bind.CollectionTypeAdapterFactorySAdapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactorySAdapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeW/rapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:125)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactorySAdapter.write(ReflectiveTypeAdapterFactory.java:243)
at com.google.gson.GsonsFuturelypeAdapter.write(Gson.java:976)
com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypellrapper.java:69)
at com.google.gson.internal.bind.CollectionTypeAdapterFactorySAdapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.collectionlypeAdapterFactorySAdapter.write(CollectionlypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:125)
at com.google.gson.internal.bind.RetlectivelypeAdapterFactorySAdapter.write(RetlectivelypeAdapterFactory.java:243)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypellrapper.java:69)
at com.google.ason.internal.bind.CollectionTypeAdapterFactorySAdapter.write(CollectionTvpeAdapterFactorv.iava:97)
com.google.gson.internal.bind.CollectionTypeAdapterFactorySAdapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypellrapper.java:69)
at com.google.gson.internal.bind.ReflectivelypeAdapterFactoryS1.write(ReflectiveTvoeAdapterfactorv.iava:125)
at
com.google.gson.internal.bind.ReflectiveTypeAdapterFactorySAdapter.write(ReflectiveTypeAdapterFactory.java:243)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypellrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:125)
at com.ood.e.ason.intrnal.in.rerltvadr-actoradtr.writRerlctidtr-actor.1ava:243.
at com.google.gson.Gson.toJson(Gson.java:669)
at com.google.gson.Gson.toJson(Gson.java:648)
at ora.sorinaframework.htto.converterison.GsonHttoMessageConverterwritelnternal(GsonHttoMessageConverter¡ava:103)
at org.springframework.http.converter.json.AbstractJsonHttpMessageConverter.writeInternal(AbstractJsonHttpMessageConverter.java:122)
at ora.springframework.http.converter.AbstractGenerichttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:102)
at org.springframework.web.servlet
mur method
.annotation.AbstractMessageConverterMethodProcessor.writeNithMessageConverters(AbstractMessageConverterMethodPi
at ora. sorinatramework.web.servlet.mvc.method.annotation.RequestResponseBodvMethodProcessor.handleReturnvalue(RequestResponsebodvMethodProcessor.1ava:180)
ora.springframework.web.method. suppo
rt.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
at org.springtramework.web.servlet.mvc.method.annotation.servletInvocablehandlerMethod.invokeAndhandle(ServletlnvocablemandlerMethod.java:119.
at ora.sorinatramework.web.servlet.mc.method.annotation.RequestMappinaHandlerAdapter.InvokeHandlerMethod(RequestMaoDinaHandlerAdapter.lava:891
at ora.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)

于是我便研究了下gson(2.8.0),这个问题有两个猜想:1.gson拥有缓存,当第一次初始化错后,后续再次从缓存中取,就一定会报错;2.每次处理该json时,都是用的定义类型,而非运行时类型。

gson处理json的逻辑为:
1.将Java对象初始化为一个类型树,并且放入缓存中
2.根据类型树和需要序列化的实例生成json字符串

源代码:
gson一开始会初始化几十个工厂,用于后面创建类型树,当然也可以手动往里加自己的类型工厂:
``java

      final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
      boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
      boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
      LongSerializationPolicy longSerializationPolicy,
      List<TypeAdapterFactory> typeAdapterFactories) {
    this.constructorConstructor = new ConstructorConstructor(instanceCreators);
    this.excluder = excluder;
    this.fieldNamingStrategy = fieldNamingStrategy;
    this.serializeNulls = serializeNulls;
    this.generateNonExecutableJson = generateNonExecutableGson;
    this.htmlSafe = htmlSafe;
    this.prettyPrinting = prettyPrinting;
    this.lenient = lenient;

    List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();

    // built-in type adapters that cannot be overridden
    factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
    factories.add(ObjectTypeAdapter.FACTORY);

    // the excluder must precede all adapters that handle user-defined types
    factories.add(excluder);

    // user's type adapters
    factories.addAll(typeAdapterFactories);

    // type adapters for basic platform types
    factories.add(TypeAdapters.STRING_FACTORY);
    factories.add(TypeAdapters.INTEGER_FACTORY);
    factories.add(TypeAdapters.BOOLEAN_FACTORY);
    factories.add(TypeAdapters.BYTE_FACTORY);
    factories.add(TypeAdapters.SHORT_FACTORY);
    TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
    factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
    factories.add(TypeAdapters.newFactory(double.class, Double.class,
            doubleAdapter(serializeSpecialFloatingPointValues)));
    factories.add(TypeAdapters.newFactory(float.class, Float.class,
            floatAdapter(serializeSpecialFloatingPointValues)));
    factories.add(TypeAdapters.NUMBER_FACTORY);
    factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
    factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
    factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
    factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
    factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
    factories.add(TypeAdapters.CHARACTER_FACTORY);
    factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
    factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
    factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
    factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
    factories.add(TypeAdapters.URL_FACTORY);
    factories.add(TypeAdapters.URI_FACTORY);
    factories.add(TypeAdapters.UUID_FACTORY);
    factories.add(TypeAdapters.CURRENCY_FACTORY);
    factories.add(TypeAdapters.LOCALE_FACTORY);
    factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
    factories.add(TypeAdapters.BIT_SET_FACTORY);
    factories.add(DateTypeAdapter.FACTORY);
    factories.add(TypeAdapters.CALENDAR_FACTORY);
    factories.add(TimeTypeAdapter.FACTORY);
    factories.add(SqlDateTypeAdapter.FACTORY);
    factories.add(TypeAdapters.TIMESTAMP_FACTORY);
    factories.add(ArrayTypeAdapter.FACTORY);
    factories.add(TypeAdapters.CLASS_FACTORY);

    // type adapters for composite and user-defined types
    factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
    factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
    this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
    factories.add(jsonAdapterFactory);
    factories.add(TypeAdapters.ENUM_FACTORY);
    factories.add(new ReflectiveTypeAdapterFactory(
        constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));

    this.factories = Collections.unmodifiableList(factories);
  }

我这里没有手动加入工厂,所以是44个工厂,这些工厂有的处理基本类型,有的处理原子类型,也有的处理json类型。

这里是根据factory创建类型树的,每个类型对象里包含一个构造器和一个绑定的字段列表:

@SuppressWarnings("unchecked")
  public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
    TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
    if (cached != null) {
      return (TypeAdapter<T>) cached;
    }

    Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
    boolean requiresThreadLocalCleanup = false;
    if (threadCalls == null) {
      threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
      calls.set(threadCalls);
      requiresThreadLocalCleanup = true;
    }

    // the key and value type parameters always agree
    FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }

    try {
      FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
      threadCalls.put(type, call);

      for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
      throw new IllegalArgumentException("GSON cannot handle " + type);
    } finally {
      threadCalls.remove(type);

      if (requiresThreadLocalCleanup) {
        calls.remove();
      }
    }
  }

这里是先从缓存里拿,缓存里没有就创建。和spring bean一样,这里的类型对象分为两种,一种是已完成的,一种是未完成的,已完成的类型对象会加入缓存,未完成的类型对象只会加入threadlocal,标识为ongoingCall。

初始化好类型树以后就可以开始将对象转为json字符串了,这里用到了大量的反射,包括刚才创建类型树时。从根类型对象开始处理:

@Override public void write(JsonWriter out, T value) throws IOException {
      if (value == null) {
        out.nullValue();
        return;
      }

      out.beginObject();
      try {
        for (BoundField boundField : boundFields.values()) {
          if (boundField.writeField(value)) {
            out.name(boundField.name);
            boundField.write(out, value);
          }
        }
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      }
      out.endObject();
    }
  }

这里的跟对象时我们自定义的一个Javabean对象,所以走ReflectiveTypeAdapterFactory。参数value是需要序列化为json字符串的Java对象实例,代码中的boundFields是刚才初始化的类型树模板,在writeField时用的是反射。

然后开始写字段:

@Override
  public void write(JsonWriter out, T value) throws IOException {
    // Order of preference for choosing type adapters
    // First preference: a type adapter registered for the runtime type
    // Second preference: a type adapter registered for the declared type
    // Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
    // Fourth preference: reflective type adapter for the declared type

    TypeAdapter chosen = delegate;
    Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
    if (runtimeType != type) {
      TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
      if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
        // The user registered a type adapter for the runtime type, so we will use that
        chosen = runtimeTypeAdapter;
      } else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
        // The user registered a type adapter for Base class, so we prefer it over the
        // reflective type adapter for the runtime type
        chosen = delegate;
      } else {
        // Use the type adapter for runtime type
        chosen = runtimeTypeAdapter;
      }
    }
    chosen.write(out, value);
  }

这里的delegate就是我们最开始初始化的类型对象,根据上面的问题,可以认为这里拿到的是ProductSpecData,这里的value可以认为是ProductSpecEntity。所以runtimeType得到的是ProductSpecEntity,定义的类型和运行时类型不一样了,gson果断以运行时类型为准啊,否则一定会出现开头报的那个问题。

所以导致这个问题的原因可以认为可能有两种:一是注入了自己的类型实现,使得获取运行时类型的时候,运行时类型和定义类型一致(代码是同一份,这种可能基本可以排除);二是该实例在第一次创建ProductSpecEntity时,优于某种原因将ProductSpecData当作value放入了缓存,使得报错百试百灵(缓存用的是concurrenthashmap,可以认为线程是安全的,类型对象完全是通过参数传过来的,理论上也没啥问题)。三是由于某种未知原因,缓存中ProductSpecEntity被替换成了ProductSpecData。

解决方案:
这个问题的根因目前无法定位(排除人为因素),解决方案为结束掉又问题的实例,重新替换一个新实例。

效果:
再也没有出现那个报错。

2024-02-01:
今天再次出现了这个报错,查看生产环境,有两个实例都会报这个错误,于是便开始了刨根问底模式,首先我能判断出问题的大概的地方,就是缓存了错误的对象。即:getAdapter的时候传入的参数肯定是ProductSpecEntity.class封装的TypeToken对象,所以放入缓存和threadlocal中存的也应该是以ProductSpecEntity.class生成的TypeAdapter,于是我便将缓存中的TypeAdapter实例偷偷替换成了以ProductSpecData.class为类型的TypeAdapter实例,然后果然本地环境报出了这个错误,对比生产环境的错,发现一个字符都不差。
通过查看源代码,gson在生成TypeAdapter实例的时候,type和rawtype混着用的(即生成ObjectConstructor和getBoundFields的 时候),猜测有的接口创建的TypeToken对象中的type和rawtype不一致,果然找到了这样 一个接口,经过测试发现,如果项目第一次启动时先请求的是我这个可能报错的接口,那就永远不会报这个错。 反之则永远报这个错。

最后列出最终解决方案:
项目里保持一致,都将entity转成data在返回,当然jpa中有的@manyToMany等注解可能会将关联表的数据也查出来,这个时候需要递归将这些查出来的entity也转成data再返回。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值