本文提供一段代码示例,演示如何在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注解。