SpringBoot 2.x Redis缓存乱码问题/自定义SpringBoot-Cache序列化方式
程序员文章站
2022-05-01 15:43:38
...
- 代码
@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);
}
-
结果
-
原因
- 查看数据是在何时被存入缓存中。
- 找到缓存自动配置类
CacheAutoConfiguration
- 找到Redis的自动配置类
- 缓存管理器
CacheManager
是缓存的抽象,RedisCacheManager
是对抽象的实现
- Redis缓存管理器
-
进入
RedisCacheManager
类 -
根据继承关系得知,一般通过的方法都在AbstractXXX类中
-
进入
AbstractCacheManager
类 -
Cache
类的角色与作用 -
debug类的调用关系可达:
- 进入
serializeCacheValue(cacheValue)
方法
-
cacheConfig.getValueSerializationPair()
返回的是RedisCacheConfiguration
类下的SerializationPair<Object> valueSerializationPair
,并且是通过构造方法注入进来的 -
那么把这个序列化类改成我们自定的应该就可以了
-
回到向容器中添加这个Bean的地方,可发现:
-
JDK的序列化方式
- 使用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,可发现程序已经进入了我们自定义的序列化方法
-
再查看缓存
-
乱码问题解决
在项目中使用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;