• 为什么Redis默认序列化器处理之后的key会带有乱码?


    建议打开代码跟着看

    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 extends RedisAccessor implements RedisOperations, 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 stringSerializer = new StringRedisSerializer();
    
    	private ScriptExecutor scriptExecutor;
    
    	// cache singleton objects (where possible)
    	private ValueOperations valueOps;
    	private ListOperations listOps;
    	private SetOperations setOps;
    	private ZSetOperations zSetOps;
    	private GeoOperations geoOps;
    	private HyperLogLogOperations hllOps;
    
    	/**
    	 * Constructs a new RedisTemplate 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(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 {
    
        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[]中的几位字节没有被修改,那么也就不会产生乱码了;

    最后,如有不足欢迎指正。

     

     

  • 相关阅读:
    聊一聊装饰者模式
    210. 课程表 II(leetcode210,ArrayList类型的数组创建,拓扑排序)-------------------Java实现
    机器学习的线性回归与非线性回归
    关系型数据库的问题和NoSQL数据库的应用
    【MySQL】MySQL 官方安装包形式
    Eigen::Quaternion
    openStack核心组件的工作流程
    LintCode 1601: Boats to Save People 双指针经典题
    基于三维Voronoi图划分的加权混合回归定位算法
    Spring源码分析refresh()第一篇
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/126158264