天天看点

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注解。