欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-01 15:43:38
...
  • 代码
    @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. 找到缓存自动配置类CacheAutoConfiguration
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
  • 找到Redis的自动配置类
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
  • 缓存管理器CacheManager是缓存的抽象,RedisCacheManager是对抽象的实现
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
  1. Redis缓存管理器
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
  • 进入RedisCacheManager

  • 根据继承关系得知,一般通过的方法都在AbstractXXX类中
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • 进入AbstractCacheManager
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • Cache类的角色与作用
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • debug类的调用关系可达:
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  1. 进入serializeCacheValue(cacheValue)方法
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
  • cacheConfig.getValueSerializationPair()返回的是 RedisCacheConfiguration类下的SerializationPair<Object> valueSerializationPair,并且是通过构造方法注入进来的
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

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

  • 回到向容器中添加这个Bean的地方,可发现:
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • JDK的序列化方式
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  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<T> implements RedisSerializer<T> {

    private final FastJsonRedisSerializer<T> 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<Object.Object>,
 * --可以自定义RedisCacheManager,并将redisTemplate设置成自定义的序列化工具,然后再@Cache()中使用这个自定义的RedisCacheManager
 *
 * @author futao
 * Created on 2019-03-22.
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    /**
     * 仅仅用作识别JSON.parseObject(text,class)方法
     */
    private Class<T> 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> redisCacheConfiguration) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
        this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
    }

    /**
     * 自定义序列化
     * 这里的FastJsonRedisSerializer引用的自己定义的
     * 不自定义的话redisTemplate会乱码
     */
    @Primary
    @Bean
    public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory factory) {
        //redis反序列化 开启fastJson反序列化的autoType
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<T>();
        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<String> 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序列化方式

  • 再查看缓存
    SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式

  • 乱码问题解决


在项目中使用RedisTemplate<String,T>

  • 自定义序列化类
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<T> implements RedisSerializer<T> {

    /**
     * 仅仅用作识别JSON.parseObject(text,class)方法
     */
    private Class<T> 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);
    }
}
  • 定义RedisTemplate<String,T>Bean
    /**
     * 自定义序列化
     * 这里的FastJsonRedisSerializer引用的自己定义的
     */
    @Primary
    @Bean
    public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory factory) {
        //redis反序列化 开启fastJson反序列化的autoType
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        RedisTemplate<String, T> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<T>();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        return redisTemplate;
    }
  • 使用

    @Autowired
    private RedisTemplate<String, User> redisTemplate;