redis使用fastjson序列化報錯Integer can not be cast to Long

1. 問題

??Sprongboot工程中使用RedisTemplate來操作Redis艘狭,因為默認(rèn)的序列化工具JdkSerializationRedisSerializer會導(dǎo)致存入Redis的數(shù)據(jù)帶上\xAC\xED\x00\x05t\x00這種字符掌桩,通過客戶端查看數(shù)據(jù)時很難閱讀,一般建議用其他序列化方式替換掉JdkSerializationRedisSerializer隐砸。
??因為我們的工程中都是用的alibaba的fastjson呀癣,所以選擇了FastJsonRedisSerializer這個序列化方式镣屹。使用中發(fā)現(xiàn)當(dāng)存入redis中的數(shù)據(jù)為Long類型時凉馆,取出數(shù)據(jù)就會報錯Integer can not be cast to Long(熟悉java的應(yīng)該都知道,這是一個Integer類型強(qiáng)轉(zhuǎn)Long類型的報錯)攘已。如果存入的是JavaBean炮赦,則會報錯JSONObject cannot be cast to xxxxx

redis配置類:

import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;

@Configuration
public class RedisConfig {

    @Resource
    private LettuceConnectionFactory redisConnectionFactory;

    static {
    // 這是為了把TestObj類加入fastjson的反序列化白名單
        ParserConfig.getGlobalInstance().addAccept("com.example.demo.config.TestObj");
    }

    @Bean
    public RedisTemplate initRedisTemplate() {
        RedisSerializer fastJsonJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(fastJsonJsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(fastJsonJsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

2. 尋找問題

2.1 可能的問題1

??換回JdkSerializationRedisSerializer的時候样勃,并不會報錯吠勘。說明是我們使用fastjson過程中出了問題性芬。

2.2 可能的問題2

出錯的代碼是:

ValueOperations<String, Long> valueOperation1 = redisTemplate.opsForValue();
Long res = valueOperation1.get("testSerialize1");

ValueOperations<String, TestObj> valueOperation2 = redisTemplate.opsForValue();
TestObj res = valueOperation2.get("testSerialize2");

其中TestObj是一個自定義類。
??我們檢查一下存入redis的數(shù)據(jù)的正確性看幼,即不傳入泛型批旺,手動序列化獲取數(shù)據(jù)的結(jié)果。代碼修改成以下:

ValueOperations valueOperation1 = redisTemplate.opsForValue();
Long res = Long.parseLong(valueOperation1.get("testSerialize1").toString());

ValueOperations valueOperation2 = redisTemplate.opsForValue();
TestObj res = JSON.parseObject(valueOperation2.get("testSerialize2").toString(), TestObj.class);

上面兩段代碼都可以正常運(yùn)行诵姜,說明數(shù)據(jù)沒有問題。

2.3 可能的問題3

??懷疑是泛型沒起作用搏熄。
??從get()方法開始棚唆,一步步往里面看:
ValueOperations.get() -> DefaultValueOperations.get() -> AbstractOperations.get() -> RedisTemplate.execute()

??RedisTemplate.execute()中有這么一行:

T result = action.doInRedis(connToExpose);

??繼續(xù)往下看:
AbstractOperations.this.deserializeValue() -> RedisSerializer.deserialize()

??RedisSerializer是一個接口,實(shí)現(xiàn)類就是我們使用的FastJsonRedisSerializer的deserialize()心例。
??看一下這個方法:

public T deserialize(byte[] bytes) throws SerializationException {
    if (bytes != null && bytes.length != 0) {
        try {
            return JSON.parseObject(bytes, this.fastJsonConfig.getCharset(), this.type, this.fastJsonConfig.getParserConfig(), this.fastJsonConfig.getParseProcess(), JSON.DEFAULT_PARSER_FEATURE, this.fastJsonConfig.getFeatures());
        } catch (Exception var3) {
            throw new SerializationException("Could not deserialize: " + var3.getMessage(), var3);
        }
    } else {
        return null;
    }
}

??通過debug觀察T可以看到泛型順利的傳到了這里宵凌。但是JSON.parseObject里面使用的是this.type,跟泛型T無關(guān)止后,而是新建FastJsonRedisSerializer時傳入的瞎惫,我們一般都傳入Object類。
??debug一下译株,我們發(fā)現(xiàn)

JSON.parseObject(bytes, this.fastJsonConfig.getCharset(), this.type, this.fastJsonConfig.getParserConfig(), this.fastJsonConfig.getParseProcess(), JSON.DEFAULT_PARSER_FEATURE, this.fastJsonConfig.getFeatures());

返回的是Integer類型而不是Object也不是Long瓜喇。
??此時反序列化方法返回的是泛型T,相當(dāng)于是使用強(qiáng)制類型轉(zhuǎn)換(Long)歉糜,把Integer轉(zhuǎn)換成Long乘寒,這肯定是要報錯Integer can not be cast to Long的。同樣如果序列化JavaBean匪补,這里返回的類型是JSONObject伞辛。

3. 為什么直接返回Integer或者JSONObject?

??在網(wǎng)上看到了這篇文章夯缺,發(fā)生了同樣的問題蚤氏。
??文章中提到出現(xiàn)該問題的原因是由JSON到Object中間經(jīng)過了Map,而在Map無法識別數(shù)據(jù)類型是Long踊兜,在Integer的大小范圍類就轉(zhuǎn)換成了Integer竿滨。修改代碼驗證一下:

redisTemplate.opsForValue().set("testSerialize", Integer.MAX_VALUE+55555L);
ValueOperations<String, Long> valueOperation1 = redisTemplate.opsForValue();
Long res = valueOperation1.get("testSerialize");

因為存的數(shù)據(jù)超出了Integer的范圍,所以自動轉(zhuǎn)化為Long润文,可以正常運(yùn)行姐呐。
??我們使用的序列化與反序列化方法,當(dāng)中恰好包含了Map轉(zhuǎn)化這一層典蝌。
??redis中其實(shí)是沒有數(shù)據(jù)類型的曙砂,都是字符串,使用時按照需要進(jìn)行轉(zhuǎn)換處理骏掀;另外參考json和jdk序列化的區(qū)別鸠澈,序列化得到的json字符串如果不做特殊處理也是不包含類型信息的柱告。而Map作為Java的一種集合類型,其中存儲的數(shù)據(jù)是有類型的笑陈。Map存儲反序列化結(jié)果的時候际度,并不知道是Integer還是Long,無論默認(rèn)使用Integer還是Long涵妥,總有一個會出問題乖菱;同樣Map并不知道Object所表示的JavaBaen的類型,直接使用了JSONObject蓬网。

4. 怎么解決這個問題

??不用Map轉(zhuǎn)換這個方法窒所,涉及到fastjson序列胡與反序列化方法,不大現(xiàn)實(shí)帆锋。我們找其他方法吵取。

4.1 方法1

??網(wǎng)上的教程一般都是叫我們改用fastjson序列化的時候使用FastJsonRedisSerializer,但是實(shí)際上還有一個類GenericFastJsonRedisSerializer(jackson也是這樣)锯厢。Generic的意思是一般的皮官、通用的,我們可以看到GenericFastJsonRedisSerializer源碼中序列化方法serialize实辑,其中SerializerFeature.WriteClassName的意思就是在序列化結(jié)果中寫入類型信息捺氢。

public byte[] serialize(Object object) throws SerializationException {
    if (object == null) {
        return new byte[0];
    } else {
        try {
            return JSON.toJSONBytes(object, new SerializerFeature[]{SerializerFeature.WriteClassName});
        } catch (Exception var3) {
            throw new SerializationException("Could not serialize: " + var3.getMessage(), var3);
        }
    }
}

例如對我們上面提到的自定義類TestObj進(jìn)行序列化得到的是:

{
  "@type": "com.example.demo.config.TestObj",
  "param1": "aaa",
  "param2": 99999,
  "param3": 88888
}

4.2 方法1的缺點(diǎn)

??如果所有使用這些需要保留類型信息的數(shù)據(jù)都是java客戶端,那么就沒什么問題徙菠,如果有其他語言讯沈,這個類型信息不同語言可能不通用,尤其是JavaBean的類型信息對其它語言肯定不通用婿奔。

4.3 方法2

??最開始提到的redis配置類的initRedisTemplate()方法修改為以下:

public RedisTemplate<String, TestObj> initRedisTemplate() {
      RedisSerializer fastJsonJsonRedisSerializer = new FastJsonRedisSerializer(TestObj.class);
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(fastJsonJsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(fastJsonJsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
}

??直接把TestObj傳入到反序列化方法中缺狠,這樣Map就知道是TestObj類,而不是根據(jù)Object類猜具體類型萍摊。

4.4 方法2的缺點(diǎn)

??配置不通用挤茄,每個類都需要配備一個RedisConfig。沒想出來怎么用泛型解決這個問題冰木。

4.5 沒得辦法的辦法

??如果不是必要穷劈,不要使用Long類型;
??redis中只存儲JavaBean的JSONObject對象踊沸,手動轉(zhuǎn)換JavaBean和JSONObject歇终。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逼龟,隨后出現(xiàn)的幾起案子评凝,更是在濱河造成了極大的恐慌,老刑警劉巖腺律,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕短,死亡現(xiàn)場離奇詭異宜肉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翎碑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門谬返,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人日杈,你說我怎么就攤上這事遣铝。” “怎么了达椰?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵翰蠢,是天一觀的道長。 經(jīng)常有香客問我啰劲,道長,這世上最難降的妖魔是什么檀何? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任蝇裤,我火速辦了婚禮,結(jié)果婚禮上频鉴,老公的妹妹穿的比我還像新娘栓辜。我一直安慰自己,他們只是感情好垛孔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布藕甩。 她就那樣靜靜地躺著,像睡著了一般周荐。 火紅的嫁衣襯著肌膚如雪狭莱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天概作,我揣著相機(jī)與錄音腋妙,去河邊找鬼。 笑死讯榕,一個胖子當(dāng)著我的面吹牛骤素,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愚屁,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼济竹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霎槐?” 一聲冷哼從身側(cè)響起送浊,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栽燕,沒想到半個月后罕袋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體改淑,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年浴讯,在試婚紗的時候發(fā)現(xiàn)自己被綠了朵夏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡榆纽,死狀恐怖仰猖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奈籽,我是刑警寧澤饥侵,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站衣屏,受9級特大地震影響躏升,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狼忱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一膨疏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钻弄,春花似錦佃却、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘤泪,卻和暖如春灶泵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背均芽。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工丘逸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掀宋。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓深纲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劲妙。 傳聞我的和親對象是個殘疾皇子湃鹊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容