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(); } }