使用Redis获取数据转json,解决动态泛型传参的问题
场景:
项目有两种角色需要不同的登录权限,将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; }
测试代码为
@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 也可以实现相应类型的序列化和反序列化
好处:只定义一个序列化器就可以了(通用)
这里我们也做个测试,分别用不修改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,解决动态泛型传参的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。