本文提供一段代碼示例,示範如何在Simple-Spring-Memcached中使用Protobuf序列化Java對象。主要解決的問題有:
- Protobuf序列化Map對象
- 實作SSM中CacheTranscoder接口的Protobuf實作
- 在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注解。