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

能力一般,水平有限,如有错误,请多指出。
如果对你有用点个关注给个赞呗

在这里插入图片描述

posted @ 2022-04-29 19:05  码猿笔记  阅读(174)  评论(0编辑  收藏  举报