場景:
項目有兩種角色需要不同的登錄權限,將redis做為用戶登錄信息緩存數(shù)據(jù)庫。碼一個方法,希望能夠根據(jù)傳入不用用戶實體類型來獲取相應的數(shù)據(jù)。用戶實體為:SessionEntityUser1>、SessionEntityUser2>。json使用FastJson。
先闡述遇到的幾個問題:
1、redis獲取到的數(shù)據(jù)序列化后,轉json,經常提示轉換異常(并不是每次,只是時常)。
2、不想每種用戶都書寫一個redis操作方法(顯得tai low)。
解決:
1、redis獲取到的數(shù)據(jù)序列化后,轉json,經常提示轉換異常:
先說redis有兩種獲取方式。
1)
redisTemplate.opsForValue().get(key);
2)
SessionEntity result = redisTemplate.execute(new RedisCallbackSessionEntity>() {
public SessionEntity doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializerString> serializer = getRedisSerializer();
byte[] key = serializer.serialize(s);
byte[] value = connection.get(key);
if (value == null) {
return null;
}
String json = serializer.deserialize(value);
return JSONObject.parseObject(json,SessionEntity.class);
}
});
顯然第一種的方式比較簡單。查看源碼,發(fā)現(xiàn)第一種方式底層調用的也是redisTemplate.execute方法,所以應該算是一種封裝吧。我們一直采用的是第二種方式。(第一種方式試過,也一樣會出現(xiàn)json強轉異常)。這里出現(xiàn)過json異常,懷疑是跟泛型有關。這里手動指定泛型反序列化類型。
修改后:
SessionEntity result = redisTemplate.execute(new RedisCallbackSessionEntity>() {
public SessionEntity doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializerString> serializer = getRedisSerializer();
byte[] key = serializer.serialize(s);
byte[] value = connection.get(key);
if (value == null) {
return null;
}
String json = serializer.deserialize(value);
return JSONObject.parseObject(json, new TypeReferenceSessionEntityUser>>(){});
}
});
完美~,確實解決了json強轉異常。
那么問題來了,這里的TypeReference需要手動指定明確的的實體類型,嘗試添加泛型:
SessionEntityT> result = redisTemplate.execute(new RedisCallbackSessionEntityT>>() {
public SessionEntityT> doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializerString> serializer = getRedisSerializer();
byte[] key = serializer.serialize(s);
byte[] value = connection.get(key);
if (value == null) {
return null;
}
String json = serializer.deserialize(value);
return JSONObject.parseObject(json, new TypeReferenceSessionEntityT>>(){});
}
});
看樣子是沒什么問題,而且泛型也被識別到了。 但是依舊無法通過。
2、不想每種用戶都書寫一個redis操作方法:
上面說到就算加了泛型也依舊無法通過,嘗試了多種方式依舊如此。百度了一圈,都是說使用TypeReference這個來解決,但是并沒有提及動態(tài)泛型的問題。偶然間看到文章說Fastjson不支持,所以嘗試替換成jackson。
替換后的代碼:
SessionEntityT> result = redisTemplate.execute(new RedisCallbackSessionEntityT>>() {
public SessionEntityT> doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializerString> serializer = getRedisSerializer();
byte[] key = serializer.serialize(s);
byte[] value = connection.get(key);
if (value == null) {
return null;
}
String json = serializer.deserialize(value);
ObjectMapper om = new ObjectMapper();
JavaType javatype = om.getTypeFactory().constructParametricType(SessionEntity.class, clazz);
try {
return om.readValue(json, javatype);
} catch (IOException e) {
e.printStackTrace();
}
return null;
// return JSONObject.parseObject(json, new TypeReferenceSessionEntityT>>(){});
}
});
這里使用到了jackson的ObjectMapper。ObjectMapper類是Jackson庫的主要類。它提供一些功能將轉換成Java對象匹配JSON結構,反之亦然。它使用JsonParser和JsonGenerator的實例實現(xiàn)JSON實際的讀/寫。(復制來的)發(fā)現(xiàn)問題解決。
提供的抽象方法為:
public T> SessionEntityT> get(final String s, ClassT> clazz);
調用方式為:
sessionEntityDao.get(key, User1.class); 跟 sessionEntityDao.get(key, User2.class);
由于這里使用到的是jackson-databind-2.6.0的庫,這個版本種constructParametricType這個方法已經快要過時,更高版本使用
constructParametrizedType
替換。這里我還沒嘗試過,等有空再玩。
這里問題已經解決,純粹做個筆記以供自己以后方便查閱。這里只提供自己項目中遇到的解決方式之一,相信應該還有其他方式可以解決。如果有說明錯誤的地方,請指出并見諒。
補充知識:Redis爬坑——Redis實現(xiàn)通用序列化器 解決Redis反序列化失敗
Redis默認序列化是 JdkSerializationRedisSerializer,由此可見
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
這里因為我們的項目需要更改默認序列策略為Jackson2JsonRedisSerializer讓它序列化為可視化的***json***語句
我們首先定義自己的RedisTemplate,這里我們不要為了每一個類定義一個序列化器,我們定義一個統(tǒng)一的序列化器所以這里泛型是 String,Object>,key我們使用StringRedisSerializer,value使用Jackson2JsonRedisSerializer
注釋代碼為修復反序列化bug的代碼
@Bean
public RedisTemplateString, Object> objectRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplateString, Object> template = new RedisTemplate();
Jackson2JsonRedisSerializerObject> jsonSerial = new Jackson2JsonRedisSerializer(Object.class);
// //修復反序列化bug
// ObjectMapper om = new ObjectMapper();
// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// jsonSerial.setObjectMapper(om);
template.setDefaultSerializer(jsonSerial);
template.setKeySerializer(RedisSerializer.string());
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
測試代碼為
@Test
public void redisSaveObject(){
UserDO ob = new UserDO();
ob.setName("name");
ob.setCity("city");
objectRedisTemplate.opsForValue().set("ob1",ob);
Object ob2 = objectRedisTemplate.opsForValue().get("ob1");
UserDO ob1 = (UserDO)ob2;
System.out.println(ob1);
}
運行結果為
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.hcy.core.model.UserDO
at com.hcy.core.redistest.RedisTest.redisSaveObject(RedisTest.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at
很明顯是對象強制轉換錯誤
這是因為泛型的原因,redis在序列化時候把他當成Object序列化的,所以這里反序列化為Object是可以的,但是因為這個Object沒有類型定義所以無法強轉。
解決辦法
在RedisTemplate中對序列化器Jackson2JsonRedisSerializer進行修改添加如下代碼,上文注釋了
//修復反序列化bug
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jsonSerial.setObjectMapper(om);
通過 objectMapper.enableDefaultTyping() 方法設置
即使使用 Object.class 作為 jcom.fasterxml.jackson.databind.JavaType 也可以實現(xiàn)相應類型的序列化和反序列化
好處:只定義一個序列化器就可以了(通用)
這里我們也做個測試,分別用不修改ObjectMapper的和修改了ObjectMapper的看看生成的value有啥子不一樣
運行結果:
ob1: [“com.hcy.core.model.UserDO”,{“userid”:null,“openid”:null,“name”:“name”,“city”:“city”}]
ob2: {“userid”:null,“openid”:null,“name”:“name”,“city”:“city”}
這里結果很明顯啦!?。?/p>
希望對大家有幫助?。?!
以上這篇使用Redis獲取數(shù)據(jù)轉json,解決動態(tài)泛型傳參的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
您可能感興趣的文章:- Redis緩存,泛型集合與json字符串的相互轉換實例
- Java后臺返回和處理JSon數(shù)據(jù)的方法步驟
- Java 實現(xiàn)Redis存儲復雜json格式數(shù)據(jù)并返回給前端