欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于FastJson?long?溢出問題的小結(jié)

 更新時間:2022年01月13日 17:20:26   作者:zyl06  
這篇文章主要介紹了關(guān)于FastJson?long?溢出問題的小結(jié),具有很好的參考價值,希望對大家有所幫助。

背景

嚴(yán)選項目中早期(2015年底)接入了 FastJson(版本 1.1.48.android),隨著業(yè)務(wù)發(fā)展,個別請求字段數(shù)值超出 int 范圍,暴露了 FastJson 當(dāng)前版本的這個溢出問題。

當(dāng)做總結(jié),希望其他團(tuán)隊可以趁早規(guī)避這個坑

問題1. 對象轉(zhuǎn) json 字符串錯誤

在網(wǎng)絡(luò)請求 response body 數(shù)據(jù)解析中,為了將 json 數(shù)據(jù)映射到對象上,調(diào)用了 JSON.toJSONString() 方法,而這里的數(shù)據(jù)處理出現(xiàn)了 long 數(shù)據(jù)溢出,數(shù)據(jù)發(fā)生錯誤

Object result = isArray ?
? ? ? ? JSON.parseArray(jsonObj.getJSONArray("data").toJSONString(), modelCls) :
? ? ? ? jsonObj.getObject("data", modelCls);
parseResult.setResult(result);

數(shù)組對象映射代碼看著有點怪,性能會有點浪費,因為涉及接口不多也沒想到有更好的映射方式,就沒改,輕噴。

問題2. 對象轉(zhuǎn)字節(jié)數(shù)組錯誤

網(wǎng)絡(luò)請求 request body 轉(zhuǎn)字節(jié)數(shù)組過程,調(diào)用了 JSON.toJSONBytes 接口,而當(dāng) mBodyMap 中存在 long 字段時發(fā)生了溢出。

@Override
public byte[] getContenteAsBytes() {
? ? //防止重復(fù)轉(zhuǎn)換
? ? if (mBody == null && mBodyMap.size() != 0) {
? ? ? ? mBody = JSON.toJSONBytes(mBodyMap);
? ? }
? ? return mBody;
}
//mBodyMap 數(shù)據(jù)內(nèi)容
Map<String, Object> mBodyMap = new HashMap<>();
mBodyMap.put("shipAddressId", 117645003002L);
...
InvoiceSubmitVO submit = new InvoiceSubmitVO();
submit.shipAddressId = 117645003002L;
mBodyMap.put("invoiceSubmite", submit);
//后端接收數(shù)據(jù)內(nèi)容
{
? ? "invoiceSubmite":{
? ? ? ? "shipAddressId": 117645003002,
? ? ? ? ...
? ? },
? ? "shipAddressId": 1680886010, ? ?
? ? ...
}

同樣的 2 個 long 字段 shipAddressId,一個能正常解析,一個發(fā)生了溢出。

1 問題解析

編寫測試代碼:

public static void test() {
? ? JSONObject jsonObj = new JSONObject();
? ? jsonObj.put("_int", 100);
? ? jsonObj.put("_long", 1234567890120L);
? ? jsonObj.put("_string", "string");
? ? String json0 = JSON.toJSONString(jsonObj);
? ? Log.i("TEST0", "json0 = " + json0);
? ? ? ??
? ? TestModel model = new TestModel();
? ? String json1 = JSON.toJSONString(model);
? ? Log.i("TEST1", "json1 = " + json1);
}
private static class TestModel {
? ? public int _int = 100;
? ? public long _long = 1234567890120L;
? ? public String _string = "string";
}

內(nèi)容輸出

I/TEST0: json0 = {"_int":100,"_long":1912276168,"_string":"string"}

I/TEST1: json1 = {"_int":100,"_long":1234567890120,"_string":"string"}

可以找到規(guī)律 map 中 long value 解析時,發(fā)生了溢出;而類對象中的 long 字段解析正常。

查看源碼:

// JSON.java
public String toJSONString() {
? ? SerializeWriter out = new SerializeWriter((Writer)null, DEFAULT_GENERATE_FEATURE, SerializerFeature.EMPTY);
? ? String var2;
? ? try {
? ? ? ? (new JSONSerializer(out, SerializeConfig.globalInstance)).write(this);
? ? ? ? var2 = out.toString();
? ? } finally {
? ? ? ? out.close();
? ? }
? ? return var2;
}
? ??
public static final String toJSONString(Object object, SerializerFeature... features) {
? ? SerializeWriter out = new SerializeWriter((Writer)null, DEFAULT_GENERATE_FEATURE, features);
? ? String var4;
? ? try {
? ? ? ? JSONSerializer serializer = new JSONSerializer(out, SerializeConfig.globalInstance);
? ? ? ? serializer.write(object);
? ? ? ? var4 = out.toString();
? ? } finally {
? ? ? ? out.close();
? ? }
? ? return var4;
}

可以看到,最終調(diào)用的都是 JSONSerializer.write 方法

//JSONSerializer.java
public final void write(Object object) {
? ? ...
? ? ObjectSerializer writer = this.getObjectWriter(clazz);
? ? ...
}
public ObjectSerializer getObjectWriter(Class<?> clazz) {
? ? ObjectSerializer writer = (ObjectSerializer)this.config.get(clazz);
? ? if (writer == null) {
? ? ? ? if(Map.class.isAssignableFrom(clazz)) {
? ? ? ? ? ? this.config.put(clazz, MapCodec.instance);
? ? ? ? }
? ? ? ? ...
? ? ? ? else {
? ? ? ? ? ? Class superClass;
? ? ? ? ? ? if(!clazz.isEnum() && ((superClass = clazz.getSuperclass()) == null || superClass == Object.class || !superClass.isEnum())) {
? ? ? ? ? ? ? ? if(clazz.isArray()) {
? ? ? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? else {
? ? ? ? ? ? ? ? ? ? ...
? ? ? ? ? ? ? ? ? ? this.config.put(clazz, this.config.createJavaBeanSerializer(clazz));
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ...
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? writer = (ObjectSerializer)this.config.get(clazz);
? ? }
? ? return writer;
}

可以看到 Map 對象使用 MapCodec 處理,普通 Class 對象使用 JavaBeanSerializer 處理

MapCodec 處理序列化寫入邏輯:

Class<?> clazz = value.getClass();
if(clazz == preClazz) {
? ? preWriter.write(serializer, value, entryKey, (Type)null);
} else {
? ? preClazz = clazz;
? ? preWriter = serializer.getObjectWriter(clazz);
? ? preWriter.write(serializer, value, entryKey, (Type)null);
}

針對 long 字段的序列化類可以查看得到是 IntegerCodec 類

// SerializeConfig.java
public SerializeConfig(int tableSize) {
? ? super(tableSize);
? ? ...
? ? this.put(Byte.class, IntegerCodec.instance);
? ? this.put(Short.class, IntegerCodec.instance);
? ? this.put(Integer.class, IntegerCodec.instance);
? ? this.put(Long.class, IntegerCodec.instance);
? ? ...
}

而查看 IntegerCodec 源碼就能看到問題原因:由于前面 fieldType 寫死 null 傳入,導(dǎo)致最后寫入都是 out.writeInt(value.intValue()); 出現(xiàn)了溢出。

\\IntegerCodec.java
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {
? ? SerializeWriter out = serializer.out;
? ? Number value = (Number)object;
? ? if(value == null) {
? ? ? ? ...
? ? } else {
? ? ? ? if (fieldType != Long.TYPE && fieldType != Long.class) {
? ? ? ? ? ? out.writeInt(value.intValue());
? ? ? ? } else {
? ? ? ? ? ? out.writeLong(value.longValue());
? ? ? ? }
? ? }
}

而當(dāng) long 值是一個class 字段時,查看 JavaBeanSerializer.write 方法,確實是被正確寫入。

// JavaBeanSerializer.java
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {
? ? ...
? ? if(valueGot && !propertyValueGot) {
? ? ? ? if(fieldClass != Integer.TYPE) {
? ? ? ? ? ? if(fieldClass == Long.TYPE) {
? ? ? ? ? ? ? ? serializer.out.writeLong(propertyValueLong);
? ? ? ? ? ? } else if(fieldClass == Boolean.TYPE) {
? ? ? ? ? ? ? ? ...
? ? ? ? ? ? }
? ? ? ? } else if(propertyValueInt == -2147483648) {
? ? ? ? ? ? ...
? ? ? ? }
? ? ? ? ...
? ? }
? ? ...
}

2 問題處理

2.1 使用 ValueFilter 處理

針對 JSON.toJSONString,可以調(diào)用如下方法,并設(shè)置 ValueFilter,F(xiàn)astJson 在寫入字符串之前會先調(diào)用 ValueFilter.process 方法,在該方法中修改 value 的數(shù)據(jù)類型,從而繞開有 bug 的 IntegerCodec 寫入邏輯

public static final String toJSONString(Object object, SerializeFilter filter, SerializerFeature... features)
public interface ValueFilter extends SerializeFilter {
? ? Object process(Object object, String name, Object value);
}
String json1 = JSON.toJSONString(map, new ValueFilter() {
? ? @Override
? ? public Object process(Object object, String name, Object value) {
? ? ? ? if (value instanceof Long) {
? ? ? ? ? ? return new BigInteger(String.valueOf(value));
? ? ? ? }
? ? ? ? return value;
? ? }
});

這里修改 long 類型為 BigInteger 類,而值不變,最后將寫入操作交給 BigDecimalCodec

2.2 替換有問題的 IntegerCodec

查看 SerializeConfig 源碼可以發(fā)現(xiàn)全部的 ObjectSerializer 子類都集成在 SerializeConfig 中,且內(nèi)部使用 globalInstance

public class SerializeConfig extends IdentityHashMap<ObjectSerializer> {
? ? public static final SerializeConfig globalInstance = new SerializeConfig();
? ? public ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
? ? ? ? return new JavaBeanSerializer(clazz);
? ? }
? ? public static final SerializeConfig getGlobalInstance() {
? ? ? ? return globalInstance;
? ? }
? ? public SerializeConfig() {
? ? ? ? this(1024);
? ? }
? ? ...
}

為此可以在 Application 初始化的時候替換 IntegerCodec

//MyApplication.java
@Override
public void onCreate() {
? ? super.onCreate();? ??
? ? SerializeConfig.getGlobalInstance().put(Byte.class, NewIntegerCodec.instance);
? ? SerializeConfig.getGlobalInstance().put(Short.class, NewIntegerCodec.instance);
? ? SerializeConfig.getGlobalInstance().put(Integer.class, NewIntegerCodec.instance);
? ? SerializeConfig.getGlobalInstance().put(Long.class, NewIntegerCodec.instance);
}

由于 NewIntegerCodec 用到的 SerializeWriter.features 字段是 protected,為此需要將該類放置在 com.alibaba.fastjson.serializer 包名下

2.3 升級 FastJson

現(xiàn)最新版本為 1.1.68.android(2018.07.16),查看 IntegerCodec 類,可以發(fā)現(xiàn) bug 已經(jīng)修復(fù)

//IntegerCodec.java
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {
? ? ...
? ??
? ? if (object instanceof Long) {
? ? ? ? out.writeLong(value.longValue());
? ? } else {
? ? ? ? out.writeInt(value.intValue());
? ? }? ??
? ? ...
}

綜上看起來,最佳方案是升級 FastJson,然而升級過程中還是觸發(fā)了其他的坑。

由于 nei 上定義的字段,部分?jǐn)?shù)值變量定義類型為 Number,同樣的基本類型,后端字段部分采用了裝箱類型,導(dǎo)致了和客戶端定義類型不一致(如服務(wù)端定義 Integer,客戶端定義 int)。

public static void test() {
? ? String json = "{\"code\":200,\"msg\":\"\",\"data\":{\"_long\":1234567890120,\"_string\":\"string\",\"_int\":null}}";
? ? JSONObject jsonObj = JSONObject.parseObject(json);
? ? AndroidModel AndroidModel = jsonObj.getObject("data", AndroidModel.class);
}
private static class AndroidModel {
? ? public int _int = 100;
? ? public long _long = 1234567890120L;
? ? public String _string = "string";
}

如上測試代碼,在早期版本這么定義并無問題,即便 _int 字段為 null,客戶端也能解析成初始值 100。而升級 FastJson 之后,json 字符串解析就會發(fā)生崩潰

//JavaBeanDeserializer.java
public Object createInstance(Map<String, Object> map, ParserConfig config) //
? ? ? ? ? ? ? ?throws IllegalAccessException,
? ? ? ? ? ? ? ?IllegalArgumentException,
? ? ? ? ? ? ? ?InvocationTargetException {
? ? Object object = null;
? ??
? ? if (beanInfo.creatorConstructor == null) {
? ? ? ? object = createInstance(null, clazz);
? ? ? ??
? ? ? ? for (Map.Entry<String, Object> entry : map.entrySet()) {
? ? ? ? ? ? ...
? ? ? ? ? ? if (method != null) {
? ? ? ? ? ? ? ? Type paramType = method.getGenericParameterTypes()[0];
? ? ? ? ? ? ? ? value = TypeUtils.cast(value, paramType, config);
? ? ? ? ? ? ? ? method.invoke(object, new Object[] { value });
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? Field field = fieldDeser.fieldInfo.field;
? ? ? ? ? ? ? ? Type paramType = fieldDeser.fieldInfo.fieldType;
? ? ? ? ? ? ? ? value = TypeUtils.cast(value, paramType, config);
? ? ? ? ? ? ? ? field.set(object, value);
? ? ? ? ? ? }
? ? ? ? }? ? ? ??
? ? ? ? return object;
? ? }
? ? ...
}
TypeUtils.java
@SuppressWarnings("unchecked")
public static final <T> T cast(Object obj, Type type, ParserConfig mapping) {
? ? if (obj == null) {
? ? ? ? return null;
? ? }
? ? ...
}

查看源碼可以發(fā)現(xiàn),當(dāng) json 字符串中 value 為 null 的時候,TypeUtils.cast 也直接返回 null,而在執(zhí)行 field.set(object, value); 時,將 null 強(qiáng)行設(shè)置給 int 字段,就會發(fā)生 IllegalArgumentException 異常。

而由于這個異常情況存在,導(dǎo)致客戶端無法升級 FastJson

3 小結(jié)

以上便是我們嚴(yán)選最近碰到的問題,即便是 FastJson 這么有名的庫,也存在這么明顯debug,感覺有些吃驚。然而由于服務(wù)端和客戶端 nei 上定義的字段類型不一致(裝箱和拆箱類型),而導(dǎo)致 Android 不能升級 FastJson,也警示了我們在 2 端接口協(xié)議等方面,必須要保持一致。

此外,上述解決方案 1、2,也僅僅解決了 json 序列化問題,而反序列化如 DefaultJSONParser 并不生效。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。 

相關(guān)文章

  • idea springboot 修改css,jsp不重啟實現(xiàn)頁面更新的問題

    idea springboot 修改css,jsp不重啟實現(xiàn)頁面更新的問題

    這篇文章主要介紹了idea springboot 修改css,jsp不重啟實現(xiàn)頁面更新的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-10-10
  • java 定時器Timer和TimerTask的使用詳解(執(zhí)行和暫停)

    java 定時器Timer和TimerTask的使用詳解(執(zhí)行和暫停)

    這篇文章主要介紹了java 定時器Timer和TimerTask的使用詳解(執(zhí)行和暫停),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-11-11
  • Spring Boot整合Mybatis Plus和Swagger2的教程詳解

    Spring Boot整合Mybatis Plus和Swagger2的教程詳解

    這篇文章主要介紹了Spring Boot整合Mybatis Plus和Swagger2的教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • 淺談Spring 的Controller 是單例or多例

    淺談Spring 的Controller 是單例or多例

    這篇文章主要介紹了淺談Spring 的Controller 是單例or多例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Java數(shù)據(jù)類型的規(guī)則

    Java數(shù)據(jù)類型的規(guī)則

    這篇文章主要介紹了Java數(shù)據(jù)類型的規(guī)則的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-12-12
  • Java?Stream流語法示例詳解

    Java?Stream流語法示例詳解

    這篇文章主要為大家詳細(xì)介紹了Java的Stream流,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Java?超詳細(xì)講解十大排序算法面試無憂

    Java?超詳細(xì)講解十大排序算法面試無憂

    這篇文章主要介紹了Java常用的排序算法及代碼實現(xiàn),在Java開發(fā)中,對排序的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時候能夠有扎實的基礎(chǔ)能力。那Java有哪些排序算法呢?本文小編就來詳細(xì)說說Java常見的排序算法,需要的朋友可以參考一下
    2022-04-04
  • spring cloud gateway網(wǎng)關(guān)路由分配代碼實例解析

    spring cloud gateway網(wǎng)關(guān)路由分配代碼實例解析

    這篇文章主要介紹了spring cloud gateway網(wǎng)關(guān)路由分配代碼實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • java dom4j解析xml用到的幾個方法

    java dom4j解析xml用到的幾個方法

    這篇文章主要介紹了java dom4j解析xml用到的幾個方法,有需要的朋友可以參考一下
    2013-12-12
  • 解決IDEA無法下載maven依賴的問題

    解決IDEA無法下載maven依賴的問題

    這篇文章主要介紹了解決IDEA無法下載maven依賴的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評論