香港云主机最佳企业级服务商!

ADSL拨号VPS包含了中国大陆(联通,移动,电信,)

中国香港,国外拨号VPS。

当前位置:云主机 > Redis >

电信ADSL拨号VPS
联通ADSL拨号VPS
移动ADSL拨号VPS

使用Redis获取数据转json,解决动态泛型传参的问题


时间:2020-11-14 13:51 作者:admin


场景:

项目有两种角色需要不同的登录权限,将redis做为用户登录信息缓存数据库。码一个方法,希望能够根据传入不用用户实体类型来获取相应的数据。用户实体为:SessionEntity<User1>、SessionEntity<User2>。json使用FastJson。

先阐述遇到的几个问题:

1、redis获取到的数据序列化后,转json,经常提示转换异常(并不是每次,只是时常)。

2、不想每种用户都书写一个redis操作方法(显得tai low)。

解决:

1、redis获取到的数据序列化后,转json,经常提示转换异常:

先说redis有两种获取方式。

1)

redisTemplate.opsForValue().get(key);

2)

SessionEntity result = redisTemplate.execute(new RedisCallback<SessionEntity>() {   public SessionEntity doInRedis(RedisConnection connection)     throws DataAccessException {    RedisSerializer<String> 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);   }  });

显然第一种的方式比较简单。查看源码,发现第一种方式底层调用的也是redisTemplate.execute方法,所以应该算是一种封装吧。我们一直采用的是第二种方式。(第一种方式试过,也一样会出现json强转异常)。这里出现过json异常,怀疑是跟泛型有关。这里手动指定泛型反序列化类型。

修改后:

SessionEntity result = redisTemplate.execute(new RedisCallback<SessionEntity>() {   public SessionEntity doInRedis(RedisConnection connection)     throws DataAccessException {    RedisSerializer<String> 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 TypeReference<SessionEntity<User>>(){});   }  });

完美~,确实解决了json强转异常。

那么问题来了,这里的TypeReference需要手动指定明确的的实体类型,尝试添加泛型:

SessionEntity<T> result = redisTemplate.execute(new RedisCallback<SessionEntity<T>>() {   public SessionEntity<T> doInRedis(RedisConnection connection)     throws DataAccessException {    RedisSerializer<String> 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 TypeReference<SessionEntity<T>>(){});   }  });

看样子是没什么问题,而且泛型也被识别到了。 但是依旧无法通过。

2、不想每种用户都书写一个redis操作方法:

上面说到就算加了泛型也依旧无法通过,尝试了多种方式依旧如此。百度了一圈,都是说使用TypeReference这个来解决,但是并没有提及动态泛型的问题。偶然间看到文章说Fastjson不支持,所以尝试替换成jackson。

替换后的代码:

SessionEntity<T> result = redisTemplate.execute(new RedisCallback<SessionEntity<T>>() {   public SessionEntity<T> doInRedis(RedisConnection connection)     throws DataAccessException {    RedisSerializer<String> 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 TypeReference<SessionEntity<T>>(){});   }  });

这里使用到了jackson的ObjectMapper。ObjectMapper类是Jackson库的主要类。它提供一些功能将转换成Java对象匹配JSON结构,反之亦然。它使用JsonParser和JsonGenerator的实例实现JSON实际的读/写。(复制来的)发现问题解决。

提供的抽象方法为:

public <T> SessionEntity<T> get(final String s, Class<T> clazz);

调用方式为:

sessionEntityDao.get(key, User1.class); 跟 sessionEntityDao.get(key, User2.class);

由于这里使用到的是jackson-databind-2.6.0的库,这个版本种constructParametricType这个方法已经快要过时,更高版本使用

constructParametrizedType

替换。这里我还没尝试过,等有空再玩。

这里问题已经解决,纯粹做个笔记以供自己以后方便查阅。这里只提供自己项目中遇到的解决方式之一,相信应该还有其他方式可以解决。如果有说明错误的地方,请指出并见谅。

补充知识:Redis爬坑——Redis实现通用序列化器 & 解决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,这里我们不要为了每一个类定义一个序列化器,我们定义一个统一的序列化器所以这里泛型是 <String,Object>,key我们使用StringRedisSerializer,value使用Jackson2JsonRedisSerializer

注释代码为修复反序列化bug的代码

 @Bean public RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {  RedisTemplate<String, Object> template = new RedisTemplate();  Jackson2JsonRedisSerializer<Object> 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; }

测试代码为

@Testpublic 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.UserDOat 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 也可以实现相应类型的序列化和反序列化

好处:只定义一个序列化器就可以了(通用)

这里我们也做个测试,分别用不修改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”}

这里结果很明显啦!!!

希望对大家有帮助!!!

以上这篇使用Redis获取数据转json,解决动态泛型传参的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

(责任编辑:admin)






帮助中心
会员注册
找回密码
新闻中心
快捷通道
域名登录面板
虚机登录面板
云主机登录面板
关于我们
关于我们
联系我们
联系方式

售前咨询:17830004266(重庆移动)

企业QQ:383546523

《中华人民共和国工业和信息化部》 编号:ICP备00012341号

Copyright © 2002 -2018 香港云主机 版权所有
声明:香港云主机品牌标志、品牌吉祥物均已注册商标,版权所有,窃用必究

云官方微信

在线客服

  • 企业QQ: 点击这里给我发消息
  • 技术支持:383546523

  • 公司总台电话:17830004266(重庆移动)
  • 售前咨询热线:17830004266(重庆移动)