天天看點

Simple-Spring-Memcached使用Protobuf序列化Java對象

本文提供一段代碼示例,示範如何在Simple-Spring-Memcached中使用Protobuf序列化Java對象。主要解決的問題有:

  1. Protobuf序列化Map對象
  2. 實作SSM中CacheTranscoder接口的Protobuf實作
  3. 在SSM中為需要序列化的對象配置相應的Transcoder

Protobuf序列化Java對象

在項目中使用Protobuf序列化時,需要編寫.proto消息定義檔案,然後使用protoc編譯出相應的java對象。其中的Java對象可以作為一個Bean在系統中使用,但是并不推薦。我們目前采用的政策如下:

  • 項目中使用獨立的Domain對象,在類中提供一個通過Probobuf生成類為參數的構造函數
  • Protobuf生成的Java對象隻用于序列化,不允許出現在其它場合
  • 每個項目子產品維護一份.proto消息定義檔案,放在domain/protobuf目錄下,protoc編譯生成的Java對象,也放在該目錄下。結構如下:
  • Simple-Spring-Memcached使用Protobuf序列化Java對象
  • 首先編輯.proto檔案,按ProtoBuf的規則,添加需要的字段。如下: ssm.proto
    package org.colorfuldays.ssm.domain.protobuf;
     
    option java_package = "org.colorfuldays.ssm.domain.protobuf";
     
    message MapEntity{
        optional string key = 1;
        optional string value = 2;
    }
     
    message Session{
        optional int64 sessionId = 1;
        repeated MapEntity attributes = 2;
    }
               

    編輯完成後,使用protoc編譯出Java對象的源碼

    執行下面指令即可編譯中上圖中的Ssm類源碼,上面定義的Session,MapEntity生成的源碼以Ssm的内部類形式放在Ssm.java檔案中。

    protoc --java_out=/Users/star/Workspace/github/ssm-demo/src/main/java ssm.proto      

    注:–java_out必須是絕對路徑,在maven的項目中,需要定位在java目錄。

    Protobuf序列化Session對象

    在生成的Ssm.java對象中包含了為Session,MapEntity做序列化操作的對象。

    • 序列化

      通過生成代碼中各對象對應Builder類來建構對象,代碼如下:

    Ssm.Session.Builder builder = Ssm.Session.newBuilder();
    Iterator<String> iterator = session.getAttributeKeySet().iterator();
    int index = 0;
    while (iterator.hasNext()) {
        Ssm.MapEntity.Builder entityBuilder = Ssm.MapEntity.newBuilder();
        entityBuilder.setKey(iterator.next()).setValue(session.getAttribute(iterator.next()));
        builder.setAttributes(index, entityBuilder.build());
    }
    Ssm.Session = builder.build();
               

    反序列化

    反序列化比較簡單,通過parseFrom方法即可,如下:

  • try {
        Ssm.Session session = Ssm.Session.parseFrom(data.getData());
        return new Session(session);
    } catch (InvalidProtocolBufferException e) {
        LOG.error("parse session from protobuf error", e);
    }
               

    其中new Session(session) 使用的是Ssm.Session對象為參數的構造方法。

    Map在ProtoBuf中的序列化

    Protobuf并不提供原生Map類型,是以在序列化Java的Map對象時,需要自己想辦法。在示例中,先定義了一個MapEntity,然後将其作為一個repeated域定義在Session中,在生在的代碼中Session中存在一個包含MapEntity的List,類似于Java Api中Map的内部實作。通過這種變通的方式實作Map的序列化時,在序列化與反序列化時,需要手動作資料轉換。

    Protobuf序列化與SSM架構結合

    SSM架構中提供了使用者定義序列化方法的擴充點。要定制序列化方式首先需要實作CacheTranscoder接口,之後在配置檔案中添加上相應的配置。

    SSM配置的自定義Transcoder粒度非常細,可以針對每一個需要序列化的對象配置相應的Transcoder實作類。

    實作CacheTranscoder接口

    CacheTranscoder接口隻提供兩個方法:

    public CachedObject encode(T t);
    public T decode(CachedObject data);
               

    其中encode為序列化就走,decode為反序列化方法。在這個demo示例的實作代碼如下:

    SessionPBTranscoder

    public class SessionPBTranscoder implements CacheTranscoder<Session> {
        private static final Logger LOG = LoggerFactory.getLogger(SessionPBTranscoder.class);
        @Override
        public Session decode(CachedObject data) {
            try {
                Ssm.Session session = Ssm.Session.parseFrom(data.getData());
                return new Session(session);
            } catch (InvalidProtocolBufferException e) {
                LOG.error("parse session from protobuf error", e);
            }
            return null;
        }
     
        @Override
        public CachedObject encode(Session session) {
            Ssm.Session.Builder builder = Ssm.Session.newBuilder();
            Iterator<String> iterator = session.getAttributeKeySet().iterator();
            int index = 0;
            while (iterator.hasNext()) {
                Ssm.MapEntity.Builder entityBuilder = Ssm.MapEntity.newBuilder();
                entityBuilder.setKey(iterator.next()).setValue(session.getAttribute(iterator.next()));
                builder.setAttributes(index, entityBuilder.build());
            }
     
            return new CachedObjectImpl(PROTOBUF_SERIALIZED,builder.build().toByteArray());
        }
     
        private static final int PROTOBUF_SERIALIZED = 9;
    }
               

    CacheTranscoder配置

    CacheTranscoder的配置是在SSM選用的memcached client中配置的。在這裡使用的是XMemcached用戶端,配置如下:

    <bean name="defaultMemcachedClient" class="com.google.code.ssm.CacheFactory">
            <property name="cacheClientFactory">
                <bean class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl"/>
            </property>
            <property name="addressProvider">
                <bean class="com.google.code.ssm.config.DefaultAddressProvider">
                    <property name="address" value="127.0.0.1:11211"/>
                </bean>
            </property>
            <property name="configuration">
                <bean class="com.google.code.ssm.providers.CacheConfiguration">
                    <property name="consistentHashing" value="true"/>
                </bean>
            </property>
            <property name="cacheTranscoders">
                <map>
                    <entry key="org.colorfuldays.ssm.domain.BookDO" value-ref="jsonTranscoder"/>
                    <entry key="org.colorfuldays.ssm.domain.UserDO" value-ref="userProtobufTranscoder"/>
                    <entry key="org.colorfuldays.ssm.domain.Session" value-ref="sessionPBTranscoder"/>
                </map>
            </property>
        </bean>
     
        <bean name="jsonTranscoder" class="com.google.code.ssm.transcoders.JsonTranscoder">
            <constructor-arg index="0"  value="org.colorfuldays.ssm.domain.BookDO"/>
            <constructor-arg index="1">
                <ref bean="JsonObjectMapper"/>
            </constructor-arg>
            <constructor-arg index="2">
                <ref bean="longToStringTranscoder"/>
            </constructor-arg>
        </bean>
     
        <bean name="userProtobufTranscoder" class="org.colorfuldays.ssm.transcoders.ProtobufTranscoder"/>
        <bean name="sessionPBTranscoder" class="org.colorfuldays.ssm.transcoders.SessionPBTranscoder"/>
        <bean name="longToStringTranscoder" class="com.google.code.ssm.transcoders.LongToStringTranscoder"/>
        <bean name="JsonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper"/>
               

    注:上述cacheTranscoders是一個Map<Class,CacheTranscoder>對象,其中Class作為Map的key,在Spring配置中隻需要配置Class的全名即可。

    完成上述配置後,即整個過程就全部完成了。具體使用的例子請參考github裡面的源碼https://github.com/iamxhu/ssm-demo

    [update]:測試發現,在使用自定義的CacheTranscoder時必須給AOP攔截的方法加上 @UseJson注解。