【springboot】RestTemplate序列化RedisSerializer到底该选哪个

RedisTemplate是Spring Data Redis提供给用户的最高级的抽象客户端,用户可直接通过RedisTemplate对Redis进行多种操作。

在项目中使用需要引入如下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

因为redis是以key-value的形式将数据存在内存中,key就是简单的string,key似乎没有长度限制,不过原则上应该尽可能的短小且可读性强,无论是否基于持久存储,key在服务的整个生命周期中都会在内存中,因此减小key的尺寸可以有效的节约内存,同时也能优化key检索的效率。

value在redis中,存储层面仍然基于string,在逻辑层面,可以是string/set/list/map,不过redis为了性能考虑,使用不同的“encoding”数据结构类型来表示它们。(例如:linkedlist,ziplist等)。

所以可以理解为,其实redis在存储数据时,都把数据转化成了byte[]数组的形式,那么在存取数据时,需要将数据格式进行转化,那么就要用到序列化和反序列化了,这也就是为什么需要配置Serializer的原因。

尽管Redis本身支持多种数据类型,但是Redis底层还是使用二进制存储数据,所以我们需要在应用层对数据的格式进行转换,这种转换称之为序列化。

针对二进制数据的“序列化/反序列化”,Spring提供了一个顶层接口RedisSerializer,并提供了多种实现可供选择(RedisSerializer),如下所示:


下面四个是Spring自带的:

  • StringRedisSerializer
  • JdkSerializationRedisSerializer
  • Jackson2JsonRedisSerializer
  • GenericJackson2JsonRedisSerializer
  • Jackson2JsonRedisSerializer

如果项目中引入了fastjson:

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version>
</dependency>

还会看到下面2个fastjson的实现类:

  • FastJsonRedisSerializer
  • GenericFastJsonRedisSerializer

StringRedisSerializer

StringRedisSerializer的使用如下:

package com.morris.spring.boot.redis;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** test StringRedisSerializer*/
@Slf4j
public class StringRedisSerializerDemo {public static void main(String[] args) {StringRedisSerializer serializer = new StringRedisSerializer();String s = "hello";byte[] bytes = serializer.serialize(s);log.info("{} => {}", s, bytes);String str = serializer.deserialize(bytes);log.info("{} => {}", bytes, str);}}

运行结果如下:

19:36:49.744 [main] INFO com.morris.spring.boot.redis.StringRedisSerializerDemo - hello => [104, 101, 108, 108, 111]
19:36:49.747 [main] INFO com.morris.spring.boot.redis.StringRedisSerializerDemo - [104, 101, 108, 108, 111] => hello

StringRedisSerializer只是对简单的字符串序列化,可读性好,可用于key的序列化。内部就是通过String类的new String(bytes) & string.getBytes()实现的序列化,源码如下:

public String deserialize(@Nullable byte[] bytes) {return (bytes == null ? null : new String(bytes, charset));
}@Override
public byte[] serialize(@Nullable String string) {return (string == null ? null : string.getBytes(charset));
}

JdkSerializationRedisSerializer

JdkSerializationRedisSerializer的使用如下:

package com.morris.spring.boot.redis;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;/*** test JdkSerializationRedisSerializer*/
@Slf4j
public class JdkSerializationRedisSerializerDemo {public static void main(String[] args) {JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();String s = "hello";byte[] bytes = serializer.serialize(s);log.info("{} => {}", s, new String(bytes));Object str = serializer.deserialize(bytes);log.info("{} => {}", new String(bytes), str);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);log.info("{} => {}", new String(userBytes), u);}}

运行结果如下:

19:51:11.240 [main] INFO com.morris.spring.boot.redis.JdkSerializationRedisSerializerDemo - hello => �� t hello
19:51:11.290 [main] INFO com.morris.spring.boot.redis.JdkSerializationRedisSerializerDemo - �� t hello => hello
19:51:11.299 [main] INFO com.morris.spring.boot.redis.JdkSerializationRedisSerializerDemo - [196]User(name=morris, age=18) => �� sr !com.morris.spring.boot.redis.User����}1)9 L aget Ljava/lang/Integer;L namet Ljava/lang/String;xpsr java.lang.Integer⠤���8 I valuexr java.lang.Number��?��?  xp   t morris
19:51:11.300 [main] INFO com.morris.spring.boot.redis.JdkSerializationRedisSerializerDemo - �� sr !com.morris.spring.boot.redis.User����}1)9 L aget Ljava/lang/Integer;L namet Ljava/lang/String;xpsr java.lang.Integer⠤���8 I valuexr java.lang.Number��?��?  xp   t morris => User(name=morris, age=18)

JdkSerializationRedisSerializer直接使用Java提供的序列化方式,效率高,占用空间少,可读行差,被序列化的对象必须实现Serializable接口,否则会抛出异常。

JdkSerializationRedisSerializer序列化底层使用的是ObjectOutputStream:

public void serialize(Object object, OutputStream outputStream) throws IOException {if (!(object instanceof Serializable)) {throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +"but received an object of type [" + object.getClass().getName() + "]");}ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(object);objectOutputStream.flush();
}

JdkSerializationRedisSerializer反序列化底层使用的是ObjectInputStream:

public Object deserialize(InputStream inputStream) throws IOException {ObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);try {return objectInputStream.readObject();}catch (ClassNotFoundException ex) {throw new NestedIOException("Failed to deserialize object type", ex);}
}

Jackson2JsonRedisSerializer

public static void errorTest() {Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [26]User(name=morris, age=18) => {"name":"morris","age":18}log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// java.util.LinkedHashMap cannot be cast to com.morris.spring.boot.redis.Userlog.info("{} => {}", new String(userBytes), u);
}

运行结果如下:

09:41:48.251 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - [26]User(name=morris, age=18) => {"name":"morris","age":18}
3 actionable tasks: 2 executed, 1 up-to-date
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.morris.spring.boot.redis.Userat com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo.errorTest(Jackson2JsonRedisSerializerDemo.java:34)at com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo.main(Jackson2JsonRedisSerializerDemo.java:15)

直接使用Jackson2JsonRedisSerializer序列化Object任意对象时没问题,但是在进行反序列化时会抛出java.util.LinkedHashMap cannot be cast to XXX异常。

Jackson2JsonRedisSerializer创建时如果不使用Object,而是指定具体的对象User就不会抛出异常,序列化出来的json也不包含额外的信息。

public static void testNotObject() {Jackson2JsonRedisSerializer<User> serializer = new Jackson2JsonRedisSerializer<>(User.class);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [26]User(name=morris, age=18) => {"name":"morris","age":18}log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = serializer.deserialize(userBytes);// {"name":"morris","age":18} => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);
}

允许结果如下:

09:45:07.795 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - [26]User(name=morris, age=18) => {"name":"morris","age":18}
09:45:07.833 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - {"name":"morris","age":18} => User(name=morris, age=18)

这样做的缺点就是对不同的对象进行序列化时要创建不同的Jackson2JsonRedisSerializer,非常麻烦。

那么有没有一种方法能实现反序列化任意对象不抛出上面的异常呢?可以通过设置ObjectMapper的一个属性来解决。

public static void testEnableDefaultTyping() {Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(objectMapper);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [64]User(name=morris, age=18) => ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}]log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}] => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);
}

运行结果如下:

09:50:55.843 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - [64]User(name=morris, age=18) => ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}]
09:50:55.894 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}] => User(name=morris, age=18)

可以看到对象序列化后会将class的全限定名写入到结果中,这样在反序列化时就能知道需要将数据反序列化成什么对象了。

objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);的含义就是对于除了一些原生类型(String、Double、Integer、Double)类型外的非常量(non-final)类型,类型将会序列化在结果上,以便可以在JSON反序列化正确的推测出值所属的类型。

上面的api已经过时了,可以使用下面的新api:

public static void testNewApi() {Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator() ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);serializer.setObjectMapper(objectMapper);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// PROPERTY {"@class":"com.morris.spring.boot.redis.User","name":"morris","age":18} => User(name=morris, age=18)// WRAPPER_ARRAY ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}] => User(name=morris, age=18)// WRAPPER_OBJECT {"com.morris.spring.boot.redis.User":{"name":"morris","age":18}} => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);
}

运行结果如下:

09:55:32.821 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - [64]User(name=morris, age=18) => ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}]
09:55:32.878 [main] INFO com.morris.spring.boot.redis.Jackson2JsonRedisSerializerDemo - ["com.morris.spring.boot.redis.User",{"name":"morris","age":18}] => User(name=morris, age=18)

GenericJackson2JsonRedisSerializer

package com.morris.spring.boot.redis;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;/*** test GenericJackson2JsonRedisSerializer*/
@Slf4j
public class GenericJackson2JsonRedisSerializerDemo {public static void main(String[] args) {GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [71]User(name=morris, age=18) => {"@class":"com.morris.spring.boot.redis.User","name":"morris","age":18}log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// {"@class":"com.morris.spring.boot.redis.User","name":"morris","age":18} => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);}
}

运行结果如下:

10:01:37.428 [main] INFO com.morris.spring.boot.redis.GenericJackson2JsonRedisSerializerDemo - [71]User(name=morris, age=18) => {"@class":"com.morris.spring.boot.redis.User","name":"morris","age":18}
10:01:37.485 [main] INFO com.morris.spring.boot.redis.GenericJackson2JsonRedisSerializerDemo - {"@class":"com.morris.spring.boot.redis.User","name":"morris","age":18} => User(name=morris, age=18)

从运行结果可以看出GenericJackson2JsonRedisSerializer确实如他的名字那样天生就支持泛型,他也是通过将类信息序列化到结果中实现的。

从GenericJackson2JsonRedisSerializer的构造方法源码中也可以看出是通过设置ObjectMapper的属性来实现,跟我们上面手动指定的配置一样。

public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName) {this(new ObjectMapper());// simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need// the type hint embedded for deserialization using the default typing feature.registerNullValueSerializer(mapper, classPropertyTypeName);if (StringUtils.hasText(classPropertyTypeName)) {mapper.activateDefaultTypingAsProperty(mapper.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL,classPropertyTypeName);} else {mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL, As.PROPERTY);}
}

FastJsonRedisSerializer

public static void testNotObject() {FastJsonRedisSerializer<User> serializer = new FastJsonRedisSerializer<>(User.class);User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [26]User(name=morris, age=18) => {"age":18,"name":"morris"}log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// {"age":18,"name":"morris"} => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);
}

运行结果如下:

11:27:52.926 [main] INFO com.morris.spring.boot.redis.FastJsonRedisSerializerDemo - [26]User(name=morris, age=18) => {"age":18,"name":"morris"}
11:27:52.950 [main] INFO com.morris.spring.boot.redis.FastJsonRedisSerializerDemo - {"age":18,"name":"morris"} => User(name=morris, age=18)

FastJsonRedisSerializer与Jackson2JsonRedisSerializer一样不支持泛型,需要支持泛型请使用GenericFastJsonRedisSerializer。

GenericFastJsonRedisSerializer

package com.morris.spring.boot.redis;import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import lombok.extern.slf4j.Slf4j;/*** test GenericFastJsonRedisSerializer*/
@Slf4j
public class GenericFastJsonRedisSerializerDemo {public static void main(String[] args) {GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();User user = new User("morris", 18);byte[] userBytes = serializer.serialize(user);// [70]User(name=morris, age=18) => {"@type":"com.morris.spring.boot.redis.User","age":18,"name":"morris"}log.info("[{}]{} => {}", userBytes.length, user, new String(userBytes));User u = (User) serializer.deserialize(userBytes);// {"@type":"com.morris.spring.boot.redis.User","age":18,"name":"morris"} => User(name=morris, age=18)log.info("{} => {}", new String(userBytes), u);}
}

运行结果如下:

11:29:09.886 [main] INFO com.morris.spring.boot.redis.GenericFastJsonRedisSerializerDemo - [70]User(name=morris, age=18) => {"@type":"com.morris.spring.boot.redis.User","age":18,"name":"morris"}
11:29:09.915 [main] INFO com.morris.spring.boot.redis.GenericFastJsonRedisSerializerDemo - {"@type":"com.morris.spring.boot.redis.User","age":18,"name":"morris"} => User(name=morris, age=18)

GenericFastJsonRedisSerializer与GenericJackson2JsonRedisSerializer一样会将类信息序列化到结果中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/19720.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA自动添加注释作者版本时间等信息

File | Settings | Editor | Live Templates 点击加号&#xff0c;选择第二项 设置一个名称 再次点击加号&#xff0c;选择第一项 填写名称&#xff08;设置完成后再代码中输入该名称即可插入该注释内容&#xff09;&#xff0c;描述&#xff0c;及内容 /*** author 名字…

机器学习技术(五)——特征工程与模型评估

机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(2️⃣) 文章目录 机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(:two:)二、模型评估1、Accuracy score2、Confusion matrix混淆矩阵1、多值2、二值 3、Hamming loss4、Precision, recall and F…

list分段截取方法

对list 分段截取方法是一个常见的操作&#xff0c;通常用于对list数据批量操作&#xff0c;常见的场景有返回分页展示数据&#xff0c;对大数据进行分批次插入数据库等 package com.hmdp.dto;import org.apache.commons.collections4.ListUtils; import org.springframework.u…

Oracle中没有show tables;如何用指令来显示表名,Excel关于VLOOKUP函数的使用。

一、问题&#xff1a;Oracle中没有show tables;如何用指令来显示表名。 解决方案&#xff1a; owner NAPSDEV更换为owner CNAPSIIDB。NAPSDEV是用户名&#xff0c;CNAPSIIDB是数据库名。在这里&#xff0c;我想让它显示的是我在Navicat中的CNAPSIIDB数据库下的所有表的名称。所…

【数据仓库】Windows源码安装DataEase,DataEase二次开发

上文记录了DataEase入门使用指南&#xff0c;本文主要记录Windows下源码安装及二次开发步骤【数据仓库】BI看板DataEase入坑指南_wenchun001的博客-CSDN博客 改动文件 源码 GitHub release 链接: Releases dataease/dataease GitHub SDK 软件环境 后端&#xff1a; JDK …

【云原生】二进制部署k8s集群(中)搭建node节点

连接上文 在上文已经成功部署了etcd分布式数据库、master01节点&#xff0c; 本文将承接上文的内容&#xff0c;继续部署Kubernetes集群中的 worker node 节点和 CNI 网络插件 1. 部署 Worker Node 组件 1.1 work node 组件部署前需了解的节点注册机制 kubelet 采用 TLS Bo…

实操:用Flutter构建一个简单的微信天气预报小程序

​ 微信小程序是一种快速、高效的开发方式&#xff0c;Flutter则是一款强大的跨平台开发框架。结合二者&#xff0c;可以轻松地开发出功能丰富、用户体验良好的微信小程序。 这里将介绍如何使用Flutter开发一个简单的天气预报小程序&#xff0c;并提供相应的代码示例。 1. 准备…

【数学建模】常微分方程

常微分方程 博客园解释 https://www.cnblogs.com/docnan/p/8126460.html https://www.cnblogs.com/hanxi/archive/2011/12/02/2272597.html https://www.cnblogs.com/b0ttle/p/ODEaid.html matlab求解常微分方程 https://www.cnblogs.com/xxfx/p/12460628.html https://www.cn…

青岛大学_王卓老师【数据结构与算法】Week05_01_栈和队列的定义和特点1_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

uniapp 小程序 filters 过滤日期

页面效果&#xff1a; <template><view class"order-intro-item"><text class"left-label">日期</text><text class"right-info time-text">{{startClearingTime | formatData}} 至 {{endClearingTime | format…

emacs下相对行号的设置

全局设置 全局开启行号显示&#xff1a;global-display-line-numbers-mode t 并设置 display-line-numbers-type的样式: relative 相对 配置代码如下: (use-package emacs:ensure t:config (setq display-line-numbers-type relative) (global-display-line-numbers-mode t)…

HTML和CSS配合制作一个简单的登录界面

HTML和CSS配合制作一个简单的登录界面 界面HTMLCSS解释语法 界面 HTML <!DOCTYPE html> <html lang"en"> <head><title>篮球世界</title><meta charset"UTF-8"><link type"text/css" rel"styleshe…