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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…

基于zookeeper实现分布式配置中心(二)

上一篇&#xff08;基于zookeeper实现分布式配置中心&#xff08;一&#xff09;&#xff09;讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性&#xff0c;简单实现一个分布式配置中心。 配置中心的优势 1、各环境配置集中管理。 2、配置更改&#xff0c;实时推…

Redis分布式锁实战

背景 目前开发过程中&#xff0c;按照公司规范&#xff0c;需要依赖框架中的缓存组件。不得不说&#xff0c;做组件的大牛对CRUD操作的封装&#xff0c;连接池、缓存路由、缓存安全性的管控都处理的无可挑剔。但是有一个小问题&#xff0c;该组件没有对分布式锁做实现&#xff…

基于RobotFramework实现自动化测试

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;执行自动化测试chromedriver下载&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本对应…

Springboot国际化信息(i18n)解析

国际化信息理解 国际化信息也称为本地化信息 。 Java 通过 java.util.Locale 类来表示本地化对象&#xff0c;它通过 “语言类型” 和 “国家/地区” 来创建一个确定的本地化对象 。举个例子吧&#xff0c;比如在发送一个具体的请求的时候&#xff0c;在header中设置一个键值对…

C语言一看就能上手的干货!你确定你不来看吗?

本地环境设置 如果您想要设置 C 语言环境&#xff0c;您需要确保电脑上有以下两款可用的软件&#xff0c;文本编辑器和 C 编译器。 文本编辑器 这将用于输入您的程序。文本编辑器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。文本编辑器的名称…

10万码农五年的C语言笔记!你现在知道别人为什么这么优秀了吗?

c语言对许多同学来说确实是一门比较难学的课程&#xff0c;不仅抽象&#xff0c;而且繁琐&#xff0c;但这又是一门不得不学的课程。前两节可能还有兴致听一听&#xff0c;然而&#xff0c;再过几节课就是一脸蒙比。凭空要想出一道题的算法和程序&#xff0c;根本无从下手。 所…

C语言/C++编程学习:C语言环境设置!

C语言是面向过程的&#xff0c;而C&#xff0b;&#xff0b;是面向对象的 C和C的区别&#xff1a; C是一个结构化语言&#xff0c;它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程&#xff0c;对输入&#xff08;或环境条件&#xff09;进行运算处理得…

C语言指针原来也可以这么的通俗易懂!

C语言是面向过程的&#xff0c;而C&#xff0b;&#xff0b;是面向对象的 C和C的区别&#xff1a; C是一个结构化语言&#xff0c;它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程&#xff0c;对输入&#xff08;或环境条件&#xff09;进行运算处理得…

C语言过时了?你在做梦?

为什么要使用C语言&#xff1f; 在过去的四十年里&#xff0c;C语言已经成为世界上最流行、最重要的一种编程语言。 C是一种融合了控制特性的现代语言&#xff0c;而我们已发现在计算机科学的理论和实践中&#xff0c;控制特性是很重要的。其设计使得用户可以自然地采用自顶向…