RedisCacheManager设置Value序列化器技巧
CacheManager基本配置
請參考博文:springboot2.0 redis EnableCaching的配置和使用
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。還有一個不必須的緩存名稱集合參數。
protected RedisCache createCache(String cacheName) {long expiration = computeExpiration(cacheName);return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration); }在創建緩存時,通過RedisCache的構造函數傳入 redisOperations(即RedisTemplate實例)。
設置全局通用的序列化器
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;}@Overridepublic String toString() {return Objects.toStringHelper(this).add("name", name).add("age", age).toString();} }調試發現,序列化內容加入了對象的類型信息,如下。
查看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自定義了一個序列化和反序列化的工具,測試代碼如下。
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;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';} }調試發現,序列化內容加入了對象的類型信息,如下。
? 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;}@Overridepublic String toString() {return Objects.toStringHelper(this).add("name", name).add("age", age).toString();} }JdkSerializationRedisSerializer構造函數如下,序列轉換器和反序列轉換器。
/*** 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;}@Overridepublic 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())));}@Overridepublic 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 {@Overrideprotected 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;}@Overridepublic void afterPropertiesSet() {if (this.fallbackToNoOpCache) {this.cacheManagers.add(new NoOpCacheManager());}}@Overridepublic Cache getCache(String name) {for (CacheManager cacheManager : this.cacheManagers) {Cache cache = cacheManager.getCache(name);if (cache != null) {return cache;}}return null;}@Overridepublic Collection<String> getCacheNames() {Set<String> names = new LinkedHashSet<String>();for (CacheManager manager : this.cacheManagers) {names.addAll(manager.getCacheNames());}return Collections.unmodifiableSet(names);}}?
轉載于:https://www.cnblogs.com/hujunzheng/p/10084452.html
總結
以上是生活随笔為你收集整理的RedisCacheManager设置Value序列化器技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十、Python第十课——字典的些许知识
- 下一篇: 简燚大理石瓷砖是几线品牌?