1. 程式人生 > >自定義redis序列化工具

自定義redis序列化工具

我們 utils 字節數 pac keys ted ive onu 問題

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>

大功告成!這樣就自定義序列化方式了。

自定義redis序列化工具