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
能力一般,水平有限,如有错误,请多指出。
如果对你有用点个关注给个赞呗