当前位置:网站首页>为什么Redis默认序列化器处理之后的key会带有乱码?
为什么Redis默认序列化器处理之后的key会带有乱码?
2022-08-04 15:23:00 【肥肥技术宅】
建议打开代码跟着看
1、直接从yml配置中进入redis配置文件:

2、看下哪些文件用到了RedisProperties

发现只有一个类引用到了
3、进到RedisAutoConfiguration类

发现也是个自动配置的类,并且内部包含一个自动配置的静态内部类RedisConfiguration可以看到这里定义了RedisTemplate的bean,并初始化了RedisTemplate的ConnectionFactory属性
4、进入RedisTemplate

发现RedisTemplate继承了RedisAccessor并实现了两个接口 先搜一下内部有没有静态代码块发现是没有的,那么看看它的父类(Java是强继承的)
5、RedisTemplate父类:RedisAccessor

好,关键来了 RedisAccessor实现了一个接口:InitializingBean接口中只定义了一个方法:afterPropertiesSet(); 这个方法会在所有bean属性赋值之后被BeanFactory调用(注释翻译,具体为什么,后续看完springboot自动配置和bean的生命周期相关源码之后再补充)
6、那么回到RedisTemplate的父类RedisAccessor看看它里面的afterPropertiesSet()方法里做了些什么

定义了一个断言,用来判断RedisConnectionFactory是否被定义了
7、再回到RedisTemplate看看它里面的afterPropertiesSet()方法里做了些什么
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
private RedisSerializer<?> defaultSerializer;
private ClassLoader classLoader;
/**以下几种序列化器都被定义为null*/
private RedisSerializer keySerializer = null;
private RedisSerializer valueSerializer = null;
private RedisSerializer hashKeySerializer = null;
private RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = new StringRedisSerializer();
private ScriptExecutor<K> scriptExecutor;
// cache singleton objects (where possible)
private ValueOperations<K, V> valueOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;
private GeoOperations<K, V> geoOps;
private HyperLogLogOperations<K, V> hllOps;
/**
* Constructs a new <code>RedisTemplate</code> instance.
*/
public RedisTemplate() {}
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
/**这里就是默认序列化器的定义了*/
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
/**enableDefaultSerializer默认为true*/
if (enableDefaultSerializer) {
/**
*对keySerializer 、
* valueSerializer、
* hashKeySerializer、
* hashValueSerializer 赋值
*/
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<K>(this);
}
initialized = true;
}
复制代码==从代码中可以看到如果没有修改redis默认序列化器,那么redis的默认序列化器就是用当前类加载器为参数定义的一个JdkSerializationRedisSerializer对象,并且其中的keySerializer、valueSerializer、hashKeySerializer、hashValueSerializer四种序列化器都默认和defaultSerializer一样!==
8、接着进到JdkSerializationRedisSerializer

从定义默认序列化器那行代码进到的方法 先看一下SerializingConverter是个什么:Serializer是什么已经再熟悉不过了吧,那么看看构造方法中的DefaultSerializer又是什么:DefaultSerializer实现了jdk的Serializer接口并实现了里面的serialize方法,这个地方就是key最后被处理的地方。
9、以上就是redis默认序列化器的步骤,上面说到一个类加载器 因为和文章标题没有太大关系就不细说了,至于作用 既然有序列化肯定就有反序列化,那个类加载器就是用来反序列化的
10、redisTemplate.opsForValue().set()这个方法和熟悉吧,往redis里做插入操作,看看里面是怎么对key做操作的,进到RedisTemplate实现的RedisOperations接口:

找到opsForValue()方法:返回了一个ValueOperations对象,进到这个对象找到它的set方法:继续看实现了set方法的类:这里看到只有一个DefaultValueOperations类实现了set方法,跟进去:set方法的庐山真面目! 其他的不管,看下key是怎么被处理的,这里创建一个ValueDeserializingRedisCallback对象的时候把key传了进去,跟进去看下key有没有被做什么处理
只是给全局key赋值,这个时候key是ValueDeserializingRedisCallback对象的一个属性了;注意这个ValueDeserializingRedisCallback类是一个抽象类,里面的inRedis()是一个抽象方法,doInredis被final修饰 那么inRedis方法是要被实现的,而doInredis方法是不允许被修改的;
11、回到set方法:

知道key被赋给ValueDeserializingRedisCallback没有被操作后,那就是execute方法里对ValueDeserializingRedisCallback这个对象进行了操作,跟进execute方法:一直跟下来发现最终回到了RedisTemplate中的execute()方法,这个时候key是在对象action内的,找一下方法内有哪些地方用到了action这个对象:发现只有两个地方用到了,第一个地方是一个断言,用于判断action是否为null,即没有被创建,第二个是调用了action自身的doInRedis方法,还记得这个action是哪个类里面的方法吧,有点绕的话回到第10步看一下。这个action是ValueDeserializingRedisCallback的对象,那么进到ValueDeserializingRedisCallback里面看一下其中的doInRedis()方法:可以看到doInRedis()方法里面有一个rawKey()方法对key做了处理,跟进去:先看一下keySerializer()是个什么:返回一个RedisSerializer对象(Redis序列化器),继续跟进template.getKeySerializer();回到了RedisTemplate中的getKeySerializer()方法,返回了keySerializer,第7步中已经说到。没有指定默认序列化器的情况下keySerializer和defaultSerializer是一样的;既然keySerializer()不返回null,那么最终rawKey()方法返回的就是keySerializer().serialize(key);
12、跟进serialize():

这个JdkSerializationRedisSerializer熟悉吗?是不是就是初始化RedisTemplate的时候创建的那个序列化器?不记得的话回到第3步看一下;
13、看到这里好像还没有说明为什么默认序列化器处理的key为什么会带有乱码;快了,以上梳理出了key的序列化过程和默认的序列化器,那么就写个程序来调用默认序列化器处理key,看看到底哪一步搞出了乱码:
public class RedisKeySerialize<K,V> {
static {
System.out.println("=============REDIS序列化KEY工具==================");
}
public String scan(){
System.out.println("\n"+"* 请输入Key:");
return new Scanner(System.in).next();
}
/**处理key*/
public String handleKey(K key){
String returnStr = "";
final byte[] rawKey = rawKey(key);
returnStr = new String(rawKey);
return returnStr;
}
/**构造一个序列化器*/
RedisSerializer keySerializer() {
return new JdkSerializationRedisSerializer(this.getClass().getClassLoader());
}
/**序列化key*/
public byte[] rawKey(K key){
if (keySerializer() == null && key instanceof byte[]) {
return (byte[]) key;
}
return keySerializer().serialize(key);
}
public static void main(String[] args) {
RedisKeySerialize keySerialize = new RedisKeySerialize();
while (true) {
String key = keySerialize.scan();
String result = keySerialize.handleKey(key);
System.out.println("* 存于Redis的key为:");
System.out.println(result);
}
}
}
复制代码执行:

现在来一步步调试:出现了出现了,可以看到创建ByteArrayOutputStream流的时候还一切正常,但是到了构建ObjectOutputStream流的时候出现了乱码! 这个时候可以下个初步结论了:==使用Redis默认序列化器处理key,key的序列化步骤中ByteArrayOutputStream转ObjectOutputStream时会产生乱码==
那么接下来看一下为什么ByteArrayOutputStream转ObjectOutputStream时会产生乱码:

进来ObjectOutputStream的构造方法,发现传进来的ByteArrayOutputStream被转换成了BlockDataOutputStream,那么后续操作的就是BlockDataOutputStream了 即对象bout

通过debug发现直到 writeStreamHeader()方法bout中的byte数组buf中还是都全部为0,进入writeStreamHeader()方法:这里对bout做了两次writeShort()操作;STREAM_MAGIC和STREAM_VERSION是两个常量,跟进writeShort()方法:直到Bits.putShort这一步,buf中还全部都为0 再继续看一下Bits.putShort这个方法是个什么操作:
传进来的byte[]数组的第off+1位变成val的byte值,第off位变成val右移8位的byte值! 好的,继续调试看看是不是因为这个putShort把buf给修改了:

这就是为什么Redis默认序列化器处理key之后会有乱码的原因所在了。
==总结==: ==1、redis默认序列化器:JdkSerializationRedisSerializer== ==2、redis默认序列化器底层使用ByteArrayOutputStream流对key进行序列化操作== ==3、序列化key的过程中ByteArrayOutputStream转为ObjectOutputStream== ==4、ObjectOutputStream构造方法中将传入的输出流做了一个writeStreamHeader()操作,writeStreamHeader()调用Bits.putShort方法修改了流中原本为空的byte数组中的几位字节,导致原本为空的流有值==
就比如一个原本干净的瓶子装的水倒出来还是干净的水,,但一个瓶子里原本有杂质,那么倒出来的水自然也就有杂质了。如果byte[]中的几位字节没有被修改,那么也就不会产生乱码了;
最后,如有不足欢迎指正。
边栏推荐
猜你喜欢
随机推荐
IP第十六天笔记
X-ray grazing incidence focusing mirror
qt 复杂界面信号槽设计
阿尔萨斯监控平台&普罗米修斯监控平台对服务器资源的监控
C语言写简单三子棋
C# 谁改了我的代码
Why, when you added a unique index or create duplicate data?
How to fall in love with a programmer
Codeforces Round #811 A~F
【云原生 | 从零开始学Kubernetes】kubernetes之StatefulSet详解
I/O stream summary
Http-Sumggling缓存漏洞分析
数据链路层-------以太网协议
Latex 去掉行号
PTA 6-2 多项式求值
2022杭电多校4
Legal education combined with VR panorama, intuitively feel and learn the spirit of the rule of law
H5 开发内嵌页面跨域问题
HarePoint Analytics for SharePoint Online
Hangzhou Electric School Competition (Counter Attack Index)









