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);
    }
  • 結果
    image.png

  • 原因

    • 查看數據是在何時被存入緩存中。
  1. 找到緩存自動配置類CacheAutoConfiguration
    image.png
    image.png
  • 找到Redis的自動配置類
    image.png
  • 緩存管理器CacheManager是緩存的抽象,RedisCacheManager是對抽象的實現
    圖片出自尚硅谷
    image.png
  1. Redis緩存管理器
    image.png
  • 進入RedisCacheManager

  • 根據繼承關係得知,一般通過的方法都在AbstractXXX類中
    image.png

  • 進入AbstractCacheManager
    image.png

  • Cache類的角色與作用
    圖片出自尚硅谷

  • debug類的調用關係可達:
    image.png

  1. 進入serializeCacheValue(cacheValue)方法
    image.png
  • cacheConfig.getValueSerializationPair()返回的是 RedisCacheConfiguration類下的SerializationPair<Object> valueSerializationPair,並且是通過構造方法注入進來的
    image.png

  • 那麼把這個序列化類改成我們自定的應該就可以了

  • 回到向容器中添加這個Bean的地方,可發現:
    image.png

  • JDK的序列化方式
    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<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,可發現程序已經進入了我們自定義的序列化方法
    image.png

  • 再查看緩存
    image.png

  • 亂碼問題解決


在項目中使用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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章