Spring Boot 结合 Redis 序列化配置的一些问题
前言
最近在学习 Spring Boot 结合 Redis 时看了一些网上的教程,发现这些教程要么比较老,要么不知道从哪抄得,运行起来有问题。这里分享一下我最新学到的写法
默认情况下,Spring 为我们提供了一个 RedisTemplate 来进行对 Redis 的操作,但是 RedisTemplate 默认配置的是使用 Java 本机序列化。
这种序列化方式,对于操作字符串或数字来说,用起来还行,但是如果要对对象操作,就不是那么的方便了。
所以我们需要配置合适的序列化方式。在 Spring 官方的文档中,官方也建议了我们使用其他的方式来进行序列化。比如 JSON
https://docs.spring.io/spring-data/redis/docs/2.2.5.RELEASE/reference/html/#redis:serializer
配置类
配置 Jackson2JsonRedisSerializer 序列化策略
下面就开始自动配置类的书写
我使用的是 Jackson2JsonRedisSerializer 来对对象进行序列化,所以首先需要一个方法,来配置 Jackson2JsonRedisSerializer 序列化策略
private Jackson2JsonRedisSerializer<Object> serializer() { // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper();</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public</span>
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常</span>
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> jackson2JsonRedisSerializer; }</span></pre>
这里要注意的是
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
这一句,这一句非常的重要,作用是序列化时将对象全类名一起保存下来
设置之后的序列化结果如下:
[ "com.buguagaoshu.redis.model.User", { "name": "1", "age": "11", "message": "牛逼" } ]
不设置的话,序列化结果如下,将无法反序列化
{ "name": "1", "age": "11", "message": "牛逼" }
一开始,我在网上搜了一下,发现大多数教程因为时间的原因,这一句用的是
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
但当我把这段代码写入的时候,发现 Idea 提示我
着是一个过时的方法,由于我当时并不知道这句话的意思,就把这段代码注释了,觉得可能没什么用,但注释后在向 Redis 里写数据的时候,数据会变成
导致数据无法反序列化。
最后我查看了这个方法的源码,找到了
通过注释,我得到了这段代码的最新写法。
也明白了这段代码的作用。
配置 RedisTemplate
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值 redisTemplate.setValueSerializer(serializer());StringRedisSerializer stringRedisSerializer </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisSerializer(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用StringRedisSerializer来序列化和反序列化redis的key值</span>
redisTemplate.setKeySerializer(stringRedisSerializer);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> hash的key也采用String的序列化方式</span>
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// hash 的 value 序列化方式采用 jackson
redisTemplate.setHashValueSerializer(serializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
这里就没有什么需要注意的了,按照自己的需求,来配置序列化的方式
配置缓存策略
@Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // 配置序列化(解决乱码的问题) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // 缓存有效期 .entryTtl(timeToLive) // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer())) // 禁用空值 .disableCachingNullValues();</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); }</span></pre>
测试代码
@SpringBootTest public class RedisApplicationTests { @Autowired private RedisTemplate<String, Object> redisTemplate;@Test </span><span style="color: rgba(0, 0, 255, 1)">void</span> contextLoads() <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception { User user </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> User(); user.setName(</span>"15"<span style="color: rgba(0, 0, 0, 1)">); user.setAge(</span>20<span style="color: rgba(0, 0, 0, 1)">); user.setMessage(</span>"牛逼"<span style="color: rgba(0, 0, 0, 1)">); redisTemplate.opsForValue().set(user.getName(), user); User getUser </span>=<span style="color: rgba(0, 0, 0, 1)"> (User) redisTemplate.opsForValue().get(user.getName()); System.out.println(getUser); System.out.println(getUser.getMessage()); }
}
再来查看 Redis 中的数据
数据正常,并且系统也能正常的反序列化了。
完整代码
package com.buguagaoshu.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
/**
@author Pu Zhiwei {@literal puzhiweipuzhiwei@foxmail.com}
create 2020-03-17 21:08
继承 CachingConfigurerSupport,为了自定义生成 KEY 的策略。可以不继承。
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.cache.redis.time-to-live}")
private Duration timeToLive = Duration.ZERO;/**
配置 Jackson2JsonRedisSerializer 序列化策略
*/
private Jackson2JsonRedisSerializer<Object> serializer() {
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();// 指定要序列化的域,field,get 和 set, 以及修饰符范围,ANY 是都有包括 private 和 public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 指定序列化输入的类型,类必须是非 final 修饰的,final 修饰的类,比如 String,Integer 等会跑出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
redisTemplate.setValueSerializer(serializer());StringRedisSerializer stringRedisSerializer </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisSerializer(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用StringRedisSerializer来序列化和反序列化redis的key值</span>
redisTemplate.setKeySerializer(stringRedisSerializer);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> hash的key也采用String的序列化方式</span>
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// hash 的 value 序列化方式采用 jackson
redisTemplate.setHashValueSerializer(serializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}@Bean </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer</span><String> redisSerializer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisSerializer(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置序列化(解决乱码的问题)</span> RedisCacheConfiguration config =<span style="color: rgba(0, 0, 0, 1)"> RedisCacheConfiguration.defaultCacheConfig() </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 缓存有效期</span>
.entryTtl(timeToLive)
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer()))
// 禁用空值
.disableCachingNullValues();</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); }
}