[原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • 代码
    @Cacheable(cacheNames = "article",
            cacheManager = "cacheManager",
            keyGenerator = "keyGenerator",
            condition = "#id!=null && #id!=''",
            unless = "#id==1")
    @Override
    public Article byId(String id) {
        log.info("查找id为{}的文章", id);
        //调用dao层
        return articleDao.byId(id);
    }
  • 结果


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第1张图片
    image.png
  • 原因

    • 查看数据是在何时被存入缓存中。
  1. 找到缓存自动配置类CacheAutoConfiguration
    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第2张图片
    image.png

    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第3张图片
    image.png
  • 找到Redis的自动配置类


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第4张图片
    image.png
  • 缓存管理器CacheManager是缓存的抽象,RedisCacheManager是对抽象的实现
    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第5张图片
    图片出自尚硅谷

    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第6张图片
    image.png
  1. Redis缓存管理器


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第7张图片
    image.png
  • 进入RedisCacheManager

  • 根据继承关系得知,一般通过的方法都在AbstractXXX类中


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第8张图片
    image.png
  • 进入AbstractCacheManager

    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第9张图片
    image.png

  • Cache类的角色与作用

    图片出自尚硅谷

  • debug类的调用关系可达:


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第10张图片
    image.png
  1. 进入serializeCacheValue(cacheValue)方法
    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第11张图片
    image.png
  • cacheConfig.getValueSerializationPair()返回的是 RedisCacheConfiguration类下的SerializationPair valueSerializationPair,并且是通过构造方法注入进来的

    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第12张图片
    image.png

  • 那么把这个序列化类改成我们自定的应该就可以了

  • 回到向容器中添加这个Bean的地方,可发现:


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第13张图片
    image.png
  • JDK的序列化方式


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第14张图片
    image.png
    1. 使用fastjson实现自定义的序列化方式-并将JDK的序列化方式改为自定义的序列化方式-需要自定义我们自己的CacheManager
    package com.lazy.cache.redis;
    
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    /**
     * @author futao
     * Created on 2019/10/24.
     */
    public class FastJsonRedisSerializer4CacheManager implements RedisSerializer {
    
        private final FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>();
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            return fastJsonRedisSerializer.serialize(t);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            return fastJsonRedisSerializer.deserialize(bytes);
        }
    }
    
    package com.lazy.cache.redis;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.nio.charset.StandardCharsets;
    
    /**
     * 自定义Redis序列化,对于redisTemplate.opsForValue.set()有效,对注解@Cache无效,因为@Cache注解使用的是RedisTemplate,
     * --可以自定义RedisCacheManager,并将redisTemplate设置成自定义的序列化工具,然后再@Cache()中使用这个自定义的RedisCacheManager
     *
     * @author futao
     * Created on 2019-03-22.
     */
    public class FastJsonRedisSerializer implements RedisSerializer {
    
        /**
         * 仅仅用作识别JSON.parseObject(text,class)方法
         */
        private Class clazz = null;
    
        protected static final SerializerFeature[] SERIALIZER_FEATURES = new SerializerFeature[]{
                SerializerFeature.PrettyFormat
                , SerializerFeature.SkipTransientField
    //            , SerializerFeature.WriteEnumUsingName
    //            , SerializerFeature.WriteDateUseDateFormat
                , SerializerFeature.WriteNullStringAsEmpty
                , SerializerFeature.WriteNullListAsEmpty
                , SerializerFeature.WriteMapNullValue
                // 【重点】序列化的时候必须需要带上Class类型,否则反序列化的时候无法知道Class类型
                , SerializerFeature.WriteClassName
        };
    
        /**
         * 序列化
         *
         * @param t 数据
         * @return
         * @throws SerializationException
         */
        @Override
        public byte[] serialize(T t) throws SerializationException {
            return t == null ? null : JSON.toJSONString(t, SERIALIZER_FEATURES).getBytes(StandardCharsets.UTF_8);
        }
    
        /**
         * 反序列化
         * clazz为null也可以反序列化成功是因为对象在序列化的时候保存了对象的class
         *
         * @param bytes 字节数组
         * @return
         * @throws SerializationException
         */
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            return bytes == null ? null : JSON.parseObject(new String(bytes, StandardCharsets.UTF_8), clazz);
        }
    }
    
    package com.lazy.cache.redis;
    
    import com.alibaba.fastjson.parser.ParserConfig;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
    import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
    import org.springframework.boot.autoconfigure.cache.CacheProperties;
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.annotation.Order;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.util.LinkedHashSet;
    import java.util.List;
    
    /**
     * @author futao
     * Created on 2019/10/24.
     */
    @Configuration
    @Order
    @AutoConfigureAfter({CacheAutoConfiguration.class})
    @Import({CacheAutoConfiguration.class})
    public class RedisConfig {
    
        private final CacheProperties cacheProperties;
    
    
        private final CacheManagerCustomizers customizerInvoker;
    
        private final RedisCacheConfiguration redisCacheConfiguration;
    
    
        public RedisConfig(CacheProperties cacheProperties,
                           CacheManagerCustomizers customizerInvoker,
                           ObjectProvider redisCacheConfiguration) {
            this.cacheProperties = cacheProperties;
            this.customizerInvoker = customizerInvoker;
            this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
        }
    
        /**
         * 自定义序列化
         * 这里的FastJsonRedisSerializer引用的自己定义的
         * 不自定义的话redisTemplate会乱码
         */
        @Primary
        @Bean
        public  RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            //redis反序列化 开启fastJson反序列化的autoType
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
            RedisTemplate redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(factory);
            FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer();
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.setHashKeySerializer(stringRedisSerializer);
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
            return redisTemplate;
        }
    
        @Primary
        @Bean
        public KeyGenerator keyGenerator() {
            return (target, method, params) -> {
                StringBuilder sb = new StringBuilder();
                sb
                        .append(target.getClass().getSimpleName())
                        .append(":")
                        .append(method.getName());
                for (Object param : params) {
                    sb
                            .append(":")
                            .append(param);
                }
                return sb.toString();
            };
        }
    
        @Primary
        @Bean
        public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
                                              ResourceLoader resourceLoader) {
            RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                    .builder(redisConnectionFactory)
                    .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
            List cacheNames = this.cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
            }
            return this.customizerInvoker.customize(builder.build());
        }
    
    
        /**
         * 读取redisCache配置
         *
         * @param classLoader
         * @return
         */
        private RedisCacheConfiguration determineConfiguration(
                ClassLoader classLoader) {
            if (this.redisCacheConfiguration != null) {
                return this.redisCacheConfiguration;
            }
            CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
            RedisCacheConfiguration config = RedisCacheConfiguration
                    .defaultCacheConfig();
            //指定采用的序列化工具
            config = config.serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new FastJsonRedisSerializer4CacheManager<>()));
            if (redisProperties.getTimeToLive() != null) {
                config = config.entryTtl(redisProperties.getTimeToLive());
            }
            if (redisProperties.getKeyPrefix() != null) {
                config = config.prefixKeysWith(redisProperties.getKeyPrefix());
            }
            if (!redisProperties.isCacheNullValues()) {
                config = config.disableCachingNullValues();
            }
            if (!redisProperties.isUseKeyPrefix()) {
                config = config.disableKeyPrefix();
            }
            return config;
        }
    }
    
    • 再debug,可发现程序已经进入了我们自定义的序列化方法


      [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第15张图片
      image.png
    • 再查看缓存


      [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第16张图片
      image.png
    • 乱码问题解决


    在项目中使用RedisTemplate

    • 自定义序列化类
    package com.lazy.cache.redis;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.nio.charset.StandardCharsets;
    
    /**
     * @author futao
     * Created on 2019-03-22.
     */
    public class FastJsonRedisSerializer implements RedisSerializer {
    
        /**
         * 仅仅用作识别JSON.parseObject(text,class)方法
         */
        private Class clazz = null;
    
        protected static final SerializerFeature[] SERIALIZER_FEATURES = new SerializerFeature[]{
                SerializerFeature.PrettyFormat
                , SerializerFeature.SkipTransientField
    //            , SerializerFeature.WriteEnumUsingName
    //            , SerializerFeature.WriteDateUseDateFormat
                , SerializerFeature.WriteNullStringAsEmpty
                , SerializerFeature.WriteNullListAsEmpty
                , SerializerFeature.WriteMapNullValue
                // 【重点】序列化的时候必须需要带上Class类型,否则反序列化的时候无法知道Class类型
                , SerializerFeature.WriteClassName
        };
    
        /**
         * 序列化
         *
         * @param t 数据
         * @return
         * @throws SerializationException
         */
        @Override
        public byte[] serialize(T t) throws SerializationException {
            return t == null ? null : JSON.toJSONString(t, SERIALIZER_FEATURES).getBytes(StandardCharsets.UTF_8);
        }
    
        /**
         * 反序列化
         * clazz为null也可以反序列化成功是因为对象在序列化的时候保存了对象的class
         *
         * @param bytes 字节数组
         * @return
         * @throws SerializationException
         */
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            return bytes == null ? null : JSON.parseObject(new String(bytes, StandardCharsets.UTF_8), clazz);
        }
    }
    
    • 定义RedisTemplateBean
        /**
         * 自定义序列化
         * 这里的FastJsonRedisSerializer引用的自己定义的
         */
        @Primary
        @Bean
        public  RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            //redis反序列化 开启fastJson反序列化的autoType
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
            RedisTemplate redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(factory);
            FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer();
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.setHashKeySerializer(stringRedisSerializer);
            redisTemplate.setValueSerializer(fastJsonRedisSerializer);
            redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
            return redisTemplate;
        }
    
    • 使用
    
        @Autowired
        private RedisTemplate redisTemplate;
    

    我在这里等你:


    [原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式_第17张图片
    image.png

    你可能感兴趣的:([原创]SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式)