91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

FastJson?long溢出問題怎么解決

發布時間:2022-01-13 21:07:46 來源:億速云 閱讀:235 作者:iii 欄目:開發技術

這篇文章主要介紹“FastJson long溢出問題怎么解決”,在日常操作中,相信很多人在FastJson long溢出問題怎么解決問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”FastJson long溢出問題怎么解決”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    背景

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

    問題1. 對象轉 json 字符串錯誤

    在網絡請求 response body 數據解析中,為了將 json 數據映射到對象上,調用了 JSON.toJSONString() 方法,而這里的數據處理出現了 long 數據溢出,數據發生錯誤

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

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

    問題2. 對象轉字節數組錯誤

    網絡請求 request body 轉字節數組過程,調用了 JSON.toJSONBytes 接口,而當 mBodyMap 中存在 long 字段時發生了溢出。

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

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

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

    內容輸出

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

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

    可以找到規律 map 中 long value 解析時,發生了溢出;而類對象中的 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;
    }

    可以看到,最終調用的都是 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 傳入,導致最后寫入都是 out.writeInt(value.intValue()); 出現了溢出。

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

    而當 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,可以調用如下方法,并設置 ValueFilter,FastJson 在寫入字符串之前會先調用 ValueFilter.process 方法,在該方法中修改 value 的數據類型,從而繞開有 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 源碼可以發現全部的 ObjectSerializer 子類都集成在 SerializeConfig 中,且內部使用 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

    現最新版本為 1.1.68.android(2018.07.16),查看 IntegerCodec 類,可以發現 bug 已經修復

    //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,然而升級過程中還是觸發了其他的坑。

    由于 nei 上定義的字段,部分數值變量定義類型為 Number,同樣的基本類型,后端字段部分采用了裝箱類型,導致了和客戶端定義類型不一致(如服務端定義 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 字符串解析就會發生崩潰

    //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;
        }
        ...
    }

    查看源碼可以發現,當 json 字符串中 value 為 null 的時候,TypeUtils.cast 也直接返回 null,而在執行 field.set(object, value); 時,將 null 強行設置給 int 字段,就會發生 IllegalArgumentException 異常。而由于這個異常情況存在,導致客戶端無法升級 FastJson

    到此,關于“FastJson long溢出問題怎么解決”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    尖扎县| 通河县| 科技| 唐河县| 六盘水市| 石棉县| 桂阳县| 漯河市| 丰城市| 壶关县| 恩平市| 江阴市| 万宁市| 保定市| 福贡县| 旌德县| 宁南县| 迁西县| 汉寿县| 商洛市| 中卫市| 曲阳县| 西城区| 邹平县| 龙口市| 卢氏县| 江西省| 朔州市| 轮台县| 金堂县| 双牌县| 赣州市| 安国市| 宜州市| 修水县| 达拉特旗| 基隆市| 碌曲县| 永丰县| 突泉县| 边坝县|