Protobuf2使用

背景

博主最近在研究sofa-jraft的时候,看到jraft使用的protobuf,所以单独拎出来单独理解一下。

 

Protobuf语法

https://www.cnblogs.com/resentment/p/6539021.html

 

使用案例

1 添加proto文件

syntax="proto2";

package jraft;

import "enum.proto";

option java_package="com.alipay.sofa.jraft.entity1";
option java_outer_classname = "RaftOutter1";


message EntryMeta {
    required int64 term = 1;
    required EntryType type = 2;
    repeated string peers = 3;
    optional int64 data_len = 4;
    // Don't change field id of `old_peers' in the consideration of backward
    // compatibility
    repeated string old_peers = 5;
    // Checksum fot this log entry, since 1.2.6, added by boyan@antfin.com
    optional int64 checksum = 6;
    repeated string learners = 7;
    repeated string old_learners = 8;
};

message SnapshotMeta {
    required int64 last_included_index = 1;
    required int64 last_included_term = 2;
    repeated string peers = 3;
    repeated string old_peers = 4;
    repeated string learners = 5;
    repeated string old_learners = 6;
}

2 下载protoc.exe   执行:

protoc ./raft.proto --java_out=../java/

会看到在entity1目录下生成

 

3 测试

package com.alipay.sofa.jraft.entity1;

import com.alipay.sofa.jraft.entity.EnumOutter;
import com.google.protobuf.InvalidProtocolBufferException;

import java.util.Arrays;

public class PB2Byte {
    public static void main(String[] args) throws InvalidProtocolBufferException {
        RaftOutter1.EntryMeta.Builder builder = RaftOutter1.EntryMeta.newBuilder();
        // ====================================赋值================================
        builder.setType(EnumOutter.EntryType.ENTRY_TYPE_UNKNOWN);
        builder.setChecksum(4354734L);
        builder.setTerm(1);
        // builder.setPeers(0, "1");
        builder.addPeers("gdghfhf");
        builder.addOldPeers("gdghfhf1");

        builder.addLearners("7");
        builder.addOldLearners("8");
        // ====================================build对象================================
        RaftOutter1.EntryMeta entryMeta = builder.build();
        // ====================================对象序列化================================
        byte[] byteArray = entryMeta.toByteArray();
        System.out.println(Arrays.toString(byteArray));

        // ====================================反序列化================================
        RaftOutter1.EntryMeta newEntryMeta = RaftOutter1.EntryMeta.parseFrom(byteArray);
        System.out.println("newEntryMeta:" + newEntryMeta.toString());
    }
}

 

4 结果

 

FileDescriptorSet的使用

获取多个proto的FD描述符,需要使用descriptor文件,其生成的指令为  protoc --descriptor_set_out=raft.desc 

定义一个ProtobufMsgFactory,该类的目的就是为了缓存classname 与  类对象的解析方法之间的映射关系,帮助快速序列化。

package com.alipay.sofa.jraft.rpc;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.SerializationException;

import com.alipay.sofa.jraft.error.MessageClassNotFoundException;
import com.alipay.sofa.jraft.storage.io.ProtoBufFile;
import com.alipay.sofa.jraft.util.RpcFactoryHelper;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Message;

import static java.lang.invoke.MethodType.methodType;

/**
 * Protobuf message factory.
 */
public class ProtobufMsgFactory {

    private static Map<String/* class name in proto file */, MethodHandle> PARSE_METHODS_4PROTO        = new HashMap<>();
    private static Map<String/* class name in java file */, MethodHandle>  PARSE_METHODS_4J            = new HashMap<>();
    private static Map<String/* class name in java file */, MethodHandle>  DEFAULT_INSTANCE_METHODS_4J = new HashMap<>();

    /**
     * 通过protoc --descriptor_set_out=raft.desc  来解析到所有的proto文件
     * 每一个proto文件遍历所有的messageType
     * 获取到messageType的parseFrom方法
     * 保存类名与parseFrom方法的映射关系
     */
    static {
        try {
            // raft.desc文件通过命令提前生成
            final FileDescriptorSet descriptorSet = FileDescriptorSet.parseFrom(ProtoBufFile.class
                .getResourceAsStream("/raft.desc"));
            final List<FileDescriptor> resolveFDs = new ArrayList<>();
            final RaftRpcFactory rpcFactory = RpcFactoryHelper.rpcFactory();
            for (final FileDescriptorProto fdp : descriptorSet.getFileList()) {

                final FileDescriptor[] dependencies = new FileDescriptor[resolveFDs.size()];
                resolveFDs.toArray(dependencies);

                final FileDescriptor fd = FileDescriptor.buildFrom(fdp, dependencies);
                resolveFDs.add(fd);
                // getMessageTypes  表示定义在proto文件中的类型名称
                for (final Descriptor descriptor : fd.getMessageTypes()) {

                    final String className = fdp.getOptions().getJavaPackage() + "."
                                             + fdp.getOptions().getJavaOuterClassname() + "$" + descriptor.getName();
                    final Class<?> clazz = Class.forName(className);
                    // 获取到MethodHandle
                    final MethodHandle parseFromHandler = MethodHandles.lookup().findStatic(clazz, "parseFrom",
                        methodType(clazz, byte[].class));  // clazz为返回值类型    byte[].class 为参数类型
                    final MethodHandle getInstanceHandler = MethodHandles.lookup().findStatic(clazz,
                        "getDefaultInstance", methodType(clazz));
                    // FullName = jraft.SnapshotMeta
                    PARSE_METHODS_4PROTO.put(descriptor.getFullName(), parseFromHandler);
                    PARSE_METHODS_4J.put(className, parseFromHandler);
                    DEFAULT_INSTANCE_METHODS_4J.put(className, getInstanceHandler);
                    rpcFactory.registerProtobufSerializer(className, getInstanceHandler.invoke());
                }

            }
        } catch (final Throwable t) {
            t.printStackTrace(); // NOPMD
        }
    }

    public static void load() {
        if (PARSE_METHODS_4J.isEmpty() || PARSE_METHODS_4PROTO.isEmpty() || DEFAULT_INSTANCE_METHODS_4J.isEmpty()) {
            throw new IllegalStateException("Parse protocol file failed.");
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends Message> T getDefaultInstance(final String className) {
        final MethodHandle handle = DEFAULT_INSTANCE_METHODS_4J.get(className);
        if (handle == null) {
            throw new MessageClassNotFoundException(className + " not found");
        }
        try {
            return (T) handle.invoke();
        } catch (Throwable t) {
            throw new SerializationException(t);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends Message> T newMessageByJavaClassName(final String className, final byte[] bs) {
        final MethodHandle handle = PARSE_METHODS_4J.get(className);
        if (handle == null) {
            throw new MessageClassNotFoundException(className + " not found");
        }
        try {
            return (T) handle.invoke(bs);
        } catch (Throwable t) {
            throw new SerializationException(t);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends Message> T newMessageByProtoClassName(final String className, final byte[] bs) {
        final MethodHandle handle = PARSE_METHODS_4PROTO.get(className);
        if (handle == null) {
            throw new MessageClassNotFoundException(className + " not found");
        }
        try {
            return (T) handle.invoke(bs);
        } catch (Throwable t) {
            throw new SerializationException(t);
        }
    }

    public static void main(String[] args) {
        new ProtobufMsgFactory();
    }
}

 

posted @ 2021-08-30 18:55  gaojy  阅读(613)  评论(0编辑  收藏  举报