问题:生产环境多个实例代码一模一样,并且启动时间也一样(都是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再返回。