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歇终。