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 的子类实现,有兴趣的可以去看看。

redis 序列化对象问题_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:///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

能力一般,水平有限,如有错误,请多指出。

如果对你有用点个关注给个赞呗