看看序列化是怎么来的
先从<string, string>开始
redistemplate.opsforvalue().set("name", "alice");
string value = redistemplate.opsforvalue().get("name");
从 java 代码层面看,这很好理解:
set("name", "alice"):把alice存到 key 为name的位置get("name"):根据 keyname把值取出来
对应到 redis 命令层面,可以理解为:
set name alice get name
因为这里的 key 和 value 都是字符串,所以这个过程非常直观。
也正因为如此,<string, string> 是理解 redistemplate 数据转换的最好起点。
再看<string, object>这个层面
如果现在 value 不再是字符串,而是一个对象,例如:
user user = new user(1l, "alice", 18);
redistemplate.opsforvalue().set("user:1", user);
这时候就不能再简单理解成:
set user:1 user对象
因为 redis 命令层面并不认识 java 的 user 对象。
也就是说,对象不能直接原样放进 redis,必须先转换成 redis 能接受的形式。
最容易想到的一种方式,就是先把对象转成字符串,例如 json 字符串:
string userstring = jsonobject.tojsonstring(user);
redistemplate.opsforvalue().set("user:1", userstring);
这样到了 redis 命令层面,就更容易理解成:
set user:1 {"id":1,"name":"alice","age":18}
所以,这一步的核心认识是:
- string 类型之所以简单,是因为它本身就很接近 redis 命令里的值。
- object 类型之所以复杂,是因为它必须先转换成某种可表示的形式。
为什么会引出“序列化”
当我们从 **<string, string>** 进入 **<string, object>** 后,就会自然遇到这个问题:
- 字符串可以直接放,对象不能直接放,那对象要怎么变成 redis 能存的内容?
这时候就需要一个“转换过程”,这个过程本质上就是序列化,你可以直接这样理解:
- 序列化,就是把 java 对象转换成 redis 能接收、能存储的形式。
反过来,取值时再把它还原成 java 对象,这就是反序列化。
一开始可以手动做序列化
最原始、最容易理解的做法,就是自己手动转:
string userstring = jsonobject.tojsonstring(user);
redistemplate.opsforvalue().set("user:1", userstring);
取出来的时候再手动转回来:
string userstring = redistemplate.opsforvalue().get("user:1");
user user = jsonobject.parseobject(userstring, user.class);
这种做法的优点是:
- 容易理解
- 逻辑清晰
- 很适合初学阶段建立概念
但问题也很明显:
- 每次 set 都要手动
tojsonstring - 每次 get 都要手动
parseobject - 重复代码很多
- 业务代码里混入了太多转换逻辑
所以会进一步封装 redisutils
为了避免每次都手动写转换代码,就会想到封装工具类,例如:
public void set(string key, object obj) {
string str = jsonobject.tojsonstring(obj);
redistemplate.opsforvalue().set(key, str);
}
这样做的意义是:把“对象转字符串”的重复逻辑,从业务代码中抽离出去。
业务层以后就不用反复写:
jsonobject.tojsonstring(obj)
而是直接调用工具方法。
不过这里要注意一点:这仍然是你自己在 java 代码里手动做转换,只不过把它封装起来了。
再后来,就会把序列化规则直接交给 redistemplate
现在抛弃封装 redisutils 方式,直接在 redistemplate 的配置类里,给 key 和 value 设置序列化方式。
这样之后,代码就可以直接写成:
redistemplate.opsforvalue().set("user:1", user);
这时候并不是 redis 直接认识了 user 对象,而是:
- 你传进去的是
user redistemplate会按照你配置好的序列化器,先把它转换成指定格式- 然后再执行 redis 操作
所以更准确的理解应该是:不是 redis 能直接存 java 对象了,而是 redistemplate 先帮我们把对象转换好了。
这就是为什么后面我们可以直接写:
redistemplate.opsforvalue().set("user:1", user);
而不用每次都手动写 jsonobject.tojsonstring(user)。
不同场景会选不同的序列化方式
- jdk 序列化
:::color3
jdk 序列化通常是 spring 体系里常见的默认方案之一。
它的特点是可以直接处理 java 对象,但在 redis 中存出来的结果通常不可读,看起来像乱码。
更准确地说,不是它“出了乱码”,而是:jdk 序列化后的内容本来就不是给人直接阅读的文本。
所以你在 redis 可视化工具里看时,会觉得它不可读。
- json 序列化
:::color3
json 序列化会把对象转成 json 文本再存入 redis。
它的好处是:
- 可读性强
- 调试方便
- 很适合大多数业务场景
所以很多项目里,key 用字符串序列化,value 用 json 序列化,是很常见的搭配。
- 自定义序列化
:::color3
如果现成方案不能满足需求,也可以自己定义序列化规则。
也就是说,你自己决定:
- 对象怎么转成可存储内容
- 取出来时又怎么转回对象
这种方式灵活,但实现和维护成本更高。
代码示例
value 采用 jdk 序列化
package com.example.demo.config;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.serializer.jdkserializationredisserializer;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class redisjdkconfig {
@bean
public redistemplate<string, object> jdkredistemplate(redisconnectionfactory connectionfactory) {
redistemplate<string, object> redistemplate = new redistemplate<>();
redistemplate.setconnectionfactory(connectionfactory);
stringredisserializer keyserializer = new stringredisserializer();
jdkserializationredisserializer valueserializer = new jdkserializationredisserializer();
redistemplate.setkeyserializer(keyserializer);
redistemplate.sethashkeyserializer(keyserializer);
redistemplate.setvalueserializer(valueserializer);
redistemplate.sethashvalueserializer(valueserializer);
redistemplate.afterpropertiesset();
return redistemplate;
}
}
value 采用 json 序列化
package com.example.demo.config;
import com.fasterxml.jackson.annotation.jsontypeinfo;
import com.fasterxml.jackson.databind.objectmapper;
import com.fasterxml.jackson.databind.jsontype.basicpolymorphictypevalidator;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.serializer.genericjackson2jsonredisserializer;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class redisjsonconfig {
@bean
public redistemplate<string, object> jsonredistemplate(redisconnectionfactory connectionfactory) {
redistemplate<string, object> redistemplate = new redistemplate<>();
redistemplate.setconnectionfactory(connectionfactory);
stringredisserializer keyserializer = new stringredisserializer();
objectmapper objectmapper = new objectmapper();
objectmapper.activatedefaulttyping(
basicpolymorphictypevalidator.builder()
.allowifsubtype(object.class)
.build(),
objectmapper.defaulttyping.non_final,
jsontypeinfo.as.property
);
genericjackson2jsonredisserializer valueserializer =
new genericjackson2jsonredisserializer(objectmapper);
redistemplate.setkeyserializer(keyserializer);
redistemplate.sethashkeyserializer(keyserializer);
redistemplate.setvalueserializer(valueserializer);
redistemplate.sethashvalueserializer(valueserializer);
redistemplate.afterpropertiesset();
return redistemplate;
}
}
value 采用自定义序列化的代码示例
第一步:自定义 user类
package com.example.demo.model;
import java.io.serializable;
@data
@noargsconstructor
@allargsconstructor
public class user implements serializable {
private long id;
private string name;
private integer age;
}
第二步:自定义序列化器
package com.example.demo.serializer;
import com.example.demo.model.user;
import org.springframework.data.redis.serializer.redisserializer;
import org.springframework.data.redis.serializer.serializationexception;
import java.nio.charset.standardcharsets;
public class userredisserializer implements redisserializer<user> {
@override
public byte[] serialize(user user) throws serializationexception {
if (user == null) {
return new byte[0];
}
string value = user.getid() + "," + user.getname() + "," + user.getage();
return value.getbytes(standardcharsets.utf_8);
}
@override
public user deserialize(byte[] bytes) throws serializationexception {
if (bytes == null || bytes.length == 0) {
return null;
}
string value = new string(bytes, standardcharsets.utf_8);
string[] arr = value.split(",");
if (arr.length != 3) {
throw new serializationexception("user 反序列化失败,数据格式不正确: " + value);
}
user user = new user();
user.setid(long.parselong(arr[0]));
user.setname(arr[1]);
user.setage(integer.parseint(arr[2]));
return user;
}
}
第三步:配置 redistemplate
package com.example.demo.config;
import com.example.demo.model.user;
import com.example.demo.serializer.userredisserializer;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class rediscustomconfig {
@bean
public redistemplate<string, user> customredistemplate(redisconnectionfactory connectionfactory) {
redistemplate<string, user> redistemplate = new redistemplate<>();
redistemplate.setconnectionfactory(connectionfactory);
stringredisserializer keyserializer = new stringredisserializer();
userredisserializer valueserializer = new userredisserializer();
redistemplate.setkeyserializer(keyserializer);
redistemplate.sethashkeyserializer(keyserializer);
redistemplate.setvalueserializer(valueserializer);
redistemplate.sethashvalueserializer(valueserializer);
redistemplate.afterpropertiesset();
return redistemplate;
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论