redis一個優點就是可以將數據寫入到磁盤中。
我們知道寫入磁盤的數據實際上都是以字節(0101這樣的二進制數據)的形式寫入的。
這意味着如果我們要將一個對象寫入磁盤,就必須將這個對象序列化。
java的序列化機制可以參考這篇文章。
可以看到java的反序列是否成功跟serialVersionUID有很大的關系,自動生成的UID在每次編譯時就會發生變化。
如果有兩個程序共享一個redis,這個時候反序列化就會出現問題。
所以總監叫我自定義個redis序列化工具。
一、為什么Spring redis中緩存的對象需要實現 Serializable 序列化接口
查看RedisTemplate源碼,我們可以看到,在RedisTemplate中針對不同類型的數據提供了不同的序列化方式。
默認的序列化方式為JdkSerializationRedisSerializer。
而我們常用的配置為鍵采用StringRedisSerializer來序列化,value采用默認的JdkSerializationSerializer。
這里我們首先分析一下這個兩個類源碼。
1、StringRedisSerializer
public class StringRedisSerializer implements RedisSerializer<String> { private final Charset charset; public StringRedisSerializer() { this(Charset.forName("UTF8")); } public StringRedisSerializer(Charset charset) { Assert.notNull(charset); this.charset = charset; } public String deserialize(byte[] bytes) { return (bytes == null ? null : new String(bytes, charset)); } public byte[] serialize(String string) { return (string == null ? null : string.getBytes(charset)); } }
代碼很簡單,序列化方法就是直接將String轉化為byte,反序列化就是直接將byte轉化為String。
這里是不涉及serialVersionUID的(沒有要求類必須實現Serializable接口)。
那么為什么會有redis緩存的對象必須實現Serializable接口的說法呢?
原因就在默認的序列化方法 JdkSerializationSerializer 中。
2、JdkSerializationSerializer
public class JdkSerializationRedisSerializer implements RedisSerializer<Object> { private Converter<Object, byte[]> serializer = new SerializingConverter(); private Converter<byte[], Object> deserializer = new DeserializingConverter(); public Object deserialize(byte[] bytes) { if (SerializationUtils.isEmpty(bytes)) { return null; } try { return deserializer.convert(bytes); } catch (Exception ex) { throw new SerializationException("Cannot deserialize", ex); } } public byte[] serialize(Object object) { if (object == null) { return SerializationUtils.EMPTY_ARRAY; } try { return serializer.convert(object); } catch (Exception ex) { throw new SerializationException("Cannot serialize", ex); } } }
上面是JdkSerializationSerializer 的源碼。可以看到序列化的時候調用了serializer.convert方法。
下面是serializer.convert方法的源碼
public byte[] convert(Object source) { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(256); try { this.serializer.serialize(source, byteStream); return byteStream.toByteArray(); } catch (Throwable ex) { throw new SerializationFailedException("Failed to serialize object using " + this.serializer.getClass().getSimpleName(), ex); } }
默認情況下,this.serializer.serialize(source, byteStream)調用的是 DefaultSerializer 下的serialize方法。
public class DefaultSerializer implements Serializer<Object> { /** * Writes the source object to an output stream using Java Serialization. * The source object must implement {@link Serializable}. */ public void serialize(Object object, OutputStream outputStream) throws IOException { if (!(object instanceof Serializable)) { throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " + "but received an object of type [" + object.getClass().getName() + "]"); } ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); } }
從上面的代碼可以看到DefaultSerializer 下的serialize方法對Object對象的序列化方式是使用ObjectOutputStream 將對象寫入到outputStream中的。
下面是ObjectOutputStream 的API。可以看到只有支持 java.io.Serializable 序列化接口的對象才能使用ObjectOutputStream進行寫入與讀取。
這就是為什么我們使用redis緩存對象時候需要讓對象實現java.io.Serializable 序列化接口的原因。
二、定制我們的序列化工具
為了不實現序列化接口,並且緩存到redis中不是難看的字節數據,我定制了自己的序列化工具。
序列化類需要實現 RedisSerializer<Object> 接口,並注冊到Spring中。
原理很簡單,序列化的時候將對象轉換為JSONObject,然后將JSONObject轉換為String,最后轉化為byte數組。
反序列化的時候,則是將byte數組轉化為JSONObject,RedisTemplate從redis中get的對象就是JSONObject類型。
下面是我的代碼。
package com.zkxl.fep.redis; import java.nio.charset.Charset; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.util.Assert; import net.sf.json.JSONObject; public class SerializeUtil implements RedisSerializer<Object>{ static final byte[] EMPTY_ARRAY = new byte[0]; private final Charset charset; public SerializeUtil() { // TODO Auto-generated constructor stub this(Charset.forName("UTF8")); } public SerializeUtil(Charset charset) { // TODO Auto-generated constructor stub Assert.notNull(charset); this.charset = charset; } @Override public byte[] serialize(Object object){ //序列化方法 // TODO Auto-generated method stub try { JSONObject jsonObject = JSONObject.fromObject(object); String jsonString = jsonObject.toString(); return (jsonString == null ? EMPTY_ARRAY : jsonString.getBytes(charset)); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return null; } @Override public Object deserialize(byte[] bytes) throws SerializationException { //反序列化 // TODO Auto-generated method stub String objectStr = null; Object object = null; if (bytes == null) { return object; } try { objectStr = new String(bytes,charset); //byte數組轉換為String JSONObject jsonObject = JSONObject.fromObject(objectStr); //String轉化為JSONObject object = jsonObject; //返回的是JSONObject類型 取數據時候需要再次轉換一下 } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return object; } }
最后如果要在Spring中使用這個序列化方法我們還需要咋redis的配置文件中注冊一下。
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="connectionFactory" /> <!-- 鍵序列化方式 --> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <!-- 值序列化方式 --> <property name="valueSerializer"> <!-- <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> --> <!-- <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> --> <bean class="com.zkxl.fep.redis.SerializeUtil"/> </property> </bean>
大功告成!這樣就自定義序列化方式了。