RedisCacheManager設置Value序列化器技巧

CacheManager基本配置

  請參考博文:springboot2.0 redis EnableCaching的配置和使用html

RedisCacheManager構造函數

/**
 * Construct a {@link RedisCacheManager}.
 * 
 * @param redisOperations
 */
@SuppressWarnings("rawtypes")
public RedisCacheManager(RedisOperations redisOperations) {
    this(redisOperations, Collections.<String> emptyList());
}

/**
 * Construct a static {@link RedisCacheManager}, managing caches for the specified cache names only.
 * 
 * @param redisOperations
 * @param cacheNames
 * @since 1.2
 */
@SuppressWarnings("rawtypes")
public RedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
    this.redisOperations = redisOperations;
    setCacheNames(cacheNames);
}

  RedisCacheManager須要一個 RedisOperations實例,通常是RedisTemplate。還有一個沒必要須的緩存名稱集合參數。java

protected RedisCache createCache(String cacheName) {
    long expiration = computeExpiration(cacheName);
    return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration);
}

  在建立緩存時,經過RedisCache的構造函數傳入 redisOperations(即RedisTemplate實例)。redis

設置全局通用的序列化器

  GenericJackson2JsonRedisSerializer

import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
User user = new User();
user.setName("hjzgg");
user.setAge(26);

System.out.println(serializer.deserialize(serializer.serialize(user)));


public static class User {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("name", name)
                .add("age", age)
                .toString();
    }
}

  調試發現,序列化內容加入了對象的類型信息,以下。spring

  

  查看GenericJackson2JsonRedisSerializer構造函數,序列化和反序列化的實現是經過Jackson的ObjectMapper完成的。並開啓了默認類型的配置。緩存

/**
 * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
 * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
 * {@link JsonTypeInfo.Id#CLASS} will be used.
 * 
 * @param classPropertyTypeName Name of the JSON property holding type information. Can be {@literal null}.
 */
public GenericJackson2JsonRedisSerializer(String classPropertyTypeName) {

    this(new ObjectMapper());

    if (StringUtils.hasText(classPropertyTypeName)) {
        mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, classPropertyTypeName);
    } else {
        mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
    }
}

  Protostuff序列化和反序列化

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import org.springframework.data.redis.serializer.RedisSerializer;

public class ProtostuffRedisSerializer implements RedisSerializer<Object> {
    private static final Schema<ObjectWrapper> schema = RuntimeSchema.getSchema(ObjectWrapper.class);

    public ProtostuffRedisSerializer() {
    }

    public byte[] serialize(Object object) {
        if (object == null) {
            return new byte[0];
        } else {
            LinkedBuffer buffer = LinkedBuffer.allocate(512);

            byte[] var3;
            try {
                var3 = ProtostuffIOUtil.toByteArray(new ObjectWrapper(object), schema, buffer);
            } finally {
                buffer.clear();
            }

            return var3;
        }
    }

    public Object deserialize(byte[] bytes) {
        if (bytes != null && bytes.length != 0) {
            try {
                ObjectWrapper objectWrapper = new ObjectWrapper();
                ProtostuffIOUtil.mergeFrom(bytes, objectWrapper, schema);
                return objectWrapper.getObject();
            } catch (Exception var3) {
                throw new RuntimeException(var3.getMessage(), var3);
            }
        } else {
            return null;
        }
    }
}

public class ObjectWrapper {
    private Object object;

    public ObjectWrapper(Object object) {
        this.object = object;
    }

    public ObjectWrapper() {
    }

    public Object getObject() {
        return this.object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

  上面經過Protostuff自定義了一個序列化和反序列化的工具,測試代碼以下。springboot

    ProtostuffRedisSerializer serializer = new ProtostuffRedisSerializer();
    Person person = new Person();
    person.setName("hjzgg");
    person.setAge(26);

    System.out.println(serializer.deserialize(serializer.serialize(person)));
}

public static class Person {
    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  調試發現,序列化內容加入了對象的類型信息,以下。app

  

   JdkSerializationRedisSerializer

JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
User user = new User();
user.setName("hjzgg");
user.setAge(26);

System.out.println(serializer.deserialize(serializer.serialize(user)));

public static class User implements Serializable {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("name", name)
                .add("age", age)
                .toString();
    }
}

  JdkSerializationRedisSerializer構造函數以下,序列轉換器和反序列轉換器。ide

/**
 * Creates a new {@link JdkSerializationRedisSerializer} using the default class loader.
 */
public JdkSerializationRedisSerializer() {
    this(new SerializingConverter(), new DeserializingConverter());
}

  發現JdkSerializationRedisSerializer內部使用的是咱們最熟悉的ObjectInputStream和ObjectOutputStream。函數

  

  

  調試發現,序列化內容加入了對象的類型信息,以下。工具

  

  要緩存的 Java 對象必須實現 Serializable 接口,由於 Spring 會將對象先序列化再存入 Redis,好比本文中的 User 類,若是不實現 Serializable 的話將會遇到相似這種錯誤:nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.XXX.User]]。

 不一樣cache設置不一樣序列化器

  Jackson2JsonRedisSerializer

import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
Jackson2JsonRedisSerializer<?> serializer1 = new Jackson2JsonRedisSerializer<>(JacksonHelper.genJavaType(User.class));
System.out.println(serializer1.deserialize(serializer1.serialize(user)));

public static class User {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("name", name)
                .add("age", age)
                .toString();
    }
}

  Jackson2JsonRedisSerializer內部序列化過程也是經過Jackson ObjectMapper來完成的,可是序列化內容不包含對象類型信息,以下。

  

  因此,在使用Jackson2JsonRedisSerializer的時候須要指定當前cache存儲的對象類型。

  自定義RedisCacheManager 

  實現不一樣RedisCache對應不一樣的RedisTemplate(即對應不一樣的序列化器)

static class CustomRedisCacheManager extends RedisCacheManager {
    private Map<String, RedisCache> redisCaches = Maps.newConcurrentMap();
    public static final String CACHE_NAME_DEFAULT = "DEFAULT_CACHE";

    public CustomRedisCacheManager(Map<String, CustomRedisConfiguration> configurations) {
        super(configurations.get(CACHE_NAME_DEFAULT).getRedisTemplate(), configurations.keySet());
        configurations.keySet()
                .stream()
                .forEach(
                        cacheName -> redisCaches.put(cacheName, new RedisCache(cacheName
                                , null
                                , configurations.get(cacheName).getRedisTemplate()
                                , configurations.get(cacheName).duration.getSeconds()))
                );
    }

    @Override
    public Cache getCache(String cacheName) {
        return redisCaches.get(cacheName);
    }
}

  RedisCacheManager 經過加載自定義配置實現類RedisCacheConfigurationProvider獲取不一樣RedisCache的配置

@Bean
@Primary
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate, ObjectProvider<RedisCacheConfigurationProvider> provider) {
    Map<String, CustomRedisConfiguration> configurations = Maps.newHashMap();
    configurations.put(CustomRedisCacheManager.CACHE_NAME_DEFAULT, new CustomRedisConfiguration(redisTemplate, Duration.ofMinutes(20)));
    RedisCacheConfigurationProvider configurationProvider = provider.getIfAvailable();
    if (!Objects.isNull(configurationProvider)) {
        configurations.putAll(configurationProvider.resolve(redisTemplate.getConnectionFactory()));
    }
    RedisCacheManager cacheManager = new CustomRedisCacheManager(configurations);
    return cacheManager;
}

  RedisCache自定義配置提供者抽象類,根據不一樣的緩存類型設置不一樣的序列化器

public static abstract class RedisCacheConfigurationProvider {
    // key = 緩存名稱, value = 緩存時間 和 緩存類型
    protected Map<String, Pair<Duration, JavaType>> configs;

    protected abstract void initConfigs();

    public Map<String, CustomRedisConfiguration> resolve(RedisConnectionFactory connectionFactory) {
        initConfigs();
        Assert.notEmpty(configs, "RedisCacheConfigurationProvider 配置不能爲空...");
        Map<String, CustomRedisConfiguration> result = Maps.newHashMap();

        configs.forEach((cacheName, pair) -> {
            RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(connectionFactory);
            redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
            redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(pair.getValue()));
            redisTemplate.afterPropertiesSet();
            result.put(cacheName, new CustomRedisConfiguration(redisTemplate, pair.getKey()));
        });
        return result;
    }
}

  用戶根據緩存名稱設置不一樣的存儲類型

@Component
public class CouponRedisCacheConfigurationProvider extends RedisCacheConfig.RedisCacheConfigurationProvider {

    @Override
    protected void initConfigs() {
        this.configs = Maps.newHashMap();
        this.configs.put(CouponConstants.COUPON_ALL_CACHE, new Pair<>(Duration.ofHours(12), JacksonHelper.genMapType(HashMap.class, String.class, Coupon.class)));
        this.configs.put(CouponConstants.COUPON_GOOD_CACHE, new Pair<>(Duration.ofHours(12), JacksonHelper.genCollectionType(List.class, String.class)));

        this.configs.put(CouponConstants.COUPON_HANDLE_TELEPHONE_STATUS_CACHE, new Pair<>(Duration.ofHours(1), JacksonHelper.genCollectionType(List.class, CouponHandle.class)));
        this.configs.put(CouponConstants.COUPON_HANDLE_TELEPHONE_GOOD_CACHE, new Pair<>(Duration.ofHours(1), JacksonHelper.genJavaType(CompositeCouponHandle.class)));
    }
}

   CompositeCacheManager

  複合CacheManager實現了給定的委託CacheManager實例集合。容許NoOpCacheManager自動添加到列表末尾,以便在沒有後備存儲的狀況下處理緩存聲明。不然,任何自定義CacheManager也能夠扮演最後一個委託的角色,懶惰地爲任何請求的名稱建立緩存區域。注意:若是複合管理器委託的常規CacheManagers須要從getCache(String)返回null,若是它們不知道指定的緩存名稱,則容許迭代到下一個委託。可是,大多數CacheManager實現都會在請求時回退到命名緩存的延遲建立;查看具備固定緩存名稱的「靜態」模式的特定配置詳細信息(若是有)。

  經過CompositeCacheManager 能夠配置過個CacheManager,每一個CacheManager能夠配置不一樣的序列化器。

public class CompositeCacheManager implements CacheManager, InitializingBean {

    private final List<CacheManager> cacheManagers = new ArrayList<CacheManager>();

    private boolean fallbackToNoOpCache = false;


    /**
     * Construct an empty CompositeCacheManager, with delegate CacheManagers to
     * be added via the {@link #setCacheManagers "cacheManagers"} property.
     */
    public CompositeCacheManager() {
    }

    /**
     * Construct a CompositeCacheManager from the given delegate CacheManagers.
     * @param cacheManagers the CacheManagers to delegate to
     */
    public CompositeCacheManager(CacheManager... cacheManagers) {
        setCacheManagers(Arrays.asList(cacheManagers));
    }


    /**
     * Specify the CacheManagers to delegate to.
     */
    public void setCacheManagers(Collection<CacheManager> cacheManagers) {
        this.cacheManagers.addAll(cacheManagers);
    }

    /**
     * Indicate whether a {@link NoOpCacheManager} should be added at the end of the delegate list.
     * In this case, any {@code getCache} requests not handled by the configured CacheManagers will
     * be automatically handled by the {@link NoOpCacheManager} (and hence never return {@code null}).
     */
    public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) {
        this.fallbackToNoOpCache = fallbackToNoOpCache;
    }

    @Override
    public void afterPropertiesSet() {
        if (this.fallbackToNoOpCache) {
            this.cacheManagers.add(new NoOpCacheManager());
        }
    }


    @Override
    public Cache getCache(String name) {
        for (CacheManager cacheManager : this.cacheManagers) {
            Cache cache = cacheManager.getCache(name);
            if (cache != null) {
                return cache;
            }
        }
        return null;
    }

    @Override
    public Collection<String> getCacheNames() {
        Set<String> names = new LinkedHashSet<String>();
        for (CacheManager manager : this.cacheManagers) {
            names.addAll(manager.getCacheNames());
        }
        return Collections.unmodifiableSet(names);
    }

}
相關文章
相關標籤/搜索