redis 序列化对象问题
背景
最近在使用redis的发布订阅模式时,订阅类接收到的是字符串,习惯性的用JSON将字符串转成对象,结果就是各种报错,刚开始想不通,通过redis可视化工具看到的明明是JSON,把结果复制出来也是能通过JSON测试的,为什么通过发布订阅获取到的结果就不能转成对象呢?
追根溯源
为什么通过下面代码能正确获取到数据呢?
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 普通缓存获取
*/
public <T> T get(String key){
return key == null ? null : (T) redisTemplate.opsForValue().get(key);
}
所以,查看get方法源码也许能找到答案。
通过查看找到DefaultValueOperations类,DefaultValueOperations是ValueOperations的默认实现类,ValueOperations是对value进行操作的接口。
class DefaultValueOperations<K, V> extends AbstractOperations<K, V> implements ValueOperations<K, V>
查看DefaultValueOperations#get方法
@Override
public V get(Object key) {
return execute(new ValueDeserializingRedisCallback(key) {
@Override
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
return connection.get(rawKey);
}
}, true);
}
可以看到value的反序列化是通过ValueDeserializingRedisCallback实现的
// utility methods for the template internal methods
abstract class ValueDeserializingRedisCallback implements RedisCallback<V> {
private Object key;
public ValueDeserializingRedisCallback(Object key) {
this.key = key;
}
//进行value的反序列化
public final V doInRedis(RedisConnection connection) {
byte[] result = inRedis(rawKey(key), connection);
//value的反序列化
return deserializeValue(result);
}
@Nullable
protected abstract byte[] inRedis(byte[] rawKey, RedisConnection connection);
}
最终对value的反序列化是deserializeValue方法
V deserializeValue(byte[] value) {
if (valueSerializer() == null) {
return (V) value;
}
return (V) valueSerializer().deserialize(value);
}
valueSerializer()是通过RedisTemplate赋值的。
RedisSerializer valueSerializer() {
return template.getValueSerializer();
}
所以最终反序列化的源头还是在RedisTemplate里面。
RedisTemplate
RedisTemplate 部分源码:
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
//是否试用默认Serializer
private boolean enableDefaultSerializer = true;
//默认Serializer
private @Nullable RedisSerializer<?> defaultSerializer;
private @Nullable ClassLoader classLoader;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
//是否使用默认的
boolean defaultUsed = false;
if (defaultSerializer == null) {
//初始化defaultSerializer
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
//初始化完成
initialized = true;
}
}
RedisTemplate在初始化时会对为null的属性进行赋值操作,除了stringSerializer是使用StringRedisSerializer进行序列化之外,其他的Serializer都是使用默认的JdkSerializationRedisSerializer。
如果
RedisSerializer
JdkSerializationRedisSerializer 是实现了RedisSerializer接口的,如果想对redis进行序列化和反序列化也可以实现RedisSerializer接口。
redis在对对象进行序列化的时候会有一个@class字段表示这个对象所属类的全限定名。
RedisSerializer 提供了四个Serializer的实例对象:
- JdkSerializationRedisSerializer:序列化的类必须实现java.io.Serializable接口
- GenericJackson2JsonRedisSerializer:Jackson 序列化
- StringRedisSerializer:String对象的序列化
- ByteArrayRedisSerializer:字节数组
下图中出了fastjson,其他都是spring-data-redis 的子类实现,有兴趣的可以去看看。
RedisSerializer部分源码:
/**
* Obtain a {@link RedisSerializer} using java serialization with the given {@link ClassLoader}.<br />
* <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
*
* @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
* @return new instance of {@link RedisSerializer}. Never {@literal null}.
* @since 2.1
*/
static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
return new JdkSerializationRedisSerializer(classLoader);
}
/**
* Obtain a {@link RedisSerializer} that can read and write JSON using
* <a href="https://github.com/FasterXML/jackson-core">Jackson</a>.
*
* @return never {@literal null}.
* @since 2.1
*/
static RedisSerializer<Object> json() {
return new GenericJackson2JsonRedisSerializer();
}
/**
* Obtain a simple {@link java.lang.String} to {@literal byte[]} (and back) serializer using
* {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8} as the default {@link java.nio.charset.Charset}.
*
* @return never {@literal null}.
* @since 2.1
*/
static RedisSerializer<String> string() {
return StringRedisSerializer.UTF_8;
}
/**
* Obtain a {@link RedisSerializer} that passes thru {@code byte[]}.
*
* @return never {@literal null}.
* @since 2.2
*/
static RedisSerializer<byte[]> byteArray() {
return ByteArrayRedisSerializer.INSTANCE;
}
替换默认RedisSerializer
如果想替换掉默认RedisSerializer子类,只需要在注入RedisTemplate的时候给DefaultSerializer赋值即可,其他的参数也可以根据需要在注入RedisTemplate的时候赋值
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String , Object> redisTemplate(){
RedisTemplate<String, Object> template = new RedisTemplate<>();
//修改默认序列化
template.setDefaultSerializer(RedisSerializer.json());
return template;
}
}
总结
Redis为了将数据跨平台存储和通过网络传输,将对象序列化为字节数组,在获取到Redis数据时就需要反序列化为对象。
至此,上述不能通过JSON转对象的原因就找到了,通过发布订阅获取到的对象是个字节数组,通过JSON转对象的形式是行不通的,那些redis可视化工具也是将字节数组转为可视化的JSON数据。
如果想得到发布/订阅的数据就需要反序列化。
可通过以下代码进行反序列化:
@Autowired
private RedisTemplate redisTemplate;
public <T> T deserialize(String data){
RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
Object deserialize = valueSerializer.deserialize(data.getBytes(StandardCharsets.UTF_8));
return deserialize == null ? null : (T) deserialize;
}
end
能力一般,水平有限,如有错误,请多指出。
如果对你有用点个关注给个赞呗