protobuf的编解码具有性能高、传输数据量小、跨语言的特点。但按照protobuf官方文档 https://developers.google.com/protocol-buffers/docs/overview中说的,需要先用protoc编译器生成对应语言的编解码代理,然后再用代理build和parse数据。
在我的一个应用场景中,使用脚本根据配置直接生成java pojo源码和对应proto文件,并在pojo中加上自身的编解码方法。这样的话若采用调用protoc生成代理的策略显得十分笨拙,另外既然protobuf能用代理类去解编码数据,那理论上,直接编解码数据也是可以实现的,但遗憾的是google并没有提供这方面的文档说明。
经过一番google以后,在stackoverflow上有大神说Descriptor描述proto文件,也有用C++实现的直接编解码的代码,java方面的非常少哇,只有一位朋友分享出来了 http://blog.csdn.net/lufeng20/article/details/8736584在此表示十分感激。
下面是编解码代码的实现:
package miniserver.util; import static miniserver.util.ReflectionUtil.gatherAllFields; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.FileDescriptorSet; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.DescriptorValidationException; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.DynamicMessage; import com.google.protobuf.DynamicMessage.Builder; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; public class ProtoParserOrBuilder { private Map<String, Descriptor> descriptors = null; private static final String TEMP_DIR = "D://"; public static final String PROTOC_PATH = System.getProperty("user.dir") + "/protoc/protoc.exe"; private File descFile; public ProtoParserOrBuilder() { descriptors = new HashMap<String, Descriptor>(); } public ProtoParserOrBuilder(File proto) { descriptors = new HashMap<String, Descriptor>(); init(proto); } private void init(File proto) { if (descFile != null && descFile.exists()) { descFile.delete(); } this.descFile = createDescripFile(proto); FileInputStream fin = null; try { fin = new FileInputStream(descFile); FileDescriptorSet descriptorSet = FileDescriptorSet.parseFrom(fin); for (FileDescriptorProto fdp : descriptorSet.getFileList()) { FileDescriptor fd = FileDescriptor.buildFrom(fdp, new FileDescriptor[] {}); for (Descriptor descriptor : fd.getMessageTypes()) { String className = descriptor.getName(); this.descriptors.put(className, descriptor); } } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (DescriptorValidationException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (fin != null) { fin.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private File createDescripFile(File proto) { try { Runtime run = Runtime.getRuntime(); String descFileName = System.currentTimeMillis() + "FastProtoParser.desc"; String protoPath = proto.getCanonicalPath(); String protoFPath = proto.getParentFile().getAbsolutePath(); String cmd = PROTOC_PATH + " -I=" + protoFPath + " --descriptor_set_out=" + TEMP_DIR + descFileName + " " + protoPath; System.out.println(cmd); // 如果不正常终止, 则生成desc文件失败 Process p = run.exec(cmd); if (p.waitFor() != 0) { if (p.exitValue() == 1) {// p.exitValue()==0表示正常结束,1:非正常结束 throw new RuntimeException("protoc 编译器报错"); } } return new File(TEMP_DIR + descFileName); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public <T> T parse(Class<T> clazz, byte[] bytes) { String className = clazz.getSimpleName(); Descriptor desc = this.descriptors.get(className); Map<String, String> fields = new HashMap<String, String>(); try { DynamicMessage message = DynamicMessage.parseFrom(desc, bytes); Map<FieldDescriptor, Object> fieldDescs = message.getAllFields(); for (Map.Entry<FieldDescriptor, Object> entry : fieldDescs .entrySet()) { fields.put(entry.getKey().getName(), entry.getValue() .toString()); } T instance = clazz.newInstance(); List<Field> fieldList = ReflectionUtil.gatherAllFields(clazz); for (Field f : fieldList) { ReflectionUtil.fillField(fields, instance, f); } return instance; } catch (InvalidProtocolBufferException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public byte[] build(Object obj) { Class<? extends Object> clazz = obj.getClass(); String className = clazz.getSimpleName(); Descriptor desc = this.descriptors.get(className); Builder builder = DynamicMessage.newBuilder(desc); List<FieldDescriptor> fieldDescs = desc.getFields(); List<Field> fields = gatherAllFields(clazz); try { Map<String, Object> fieldValues = new HashMap<String, Object>(); for (Field field : fields) { field.setAccessible(true); String fieldName = field.getName(); Object fieldValueObject; fieldValueObject = field.get(obj); if (fieldValueObject != null) { fieldValues.put(fieldName, fieldValueObject); } } for (FieldDescriptor fieldDesc : fieldDescs) { String fieldName = fieldDesc.getName(); Object val = fieldValues.get(fieldName); if (val != null) { builder.setField(fieldDesc, val); } } Message message = builder.build(); return message.toByteArray(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } @Override protected void finalize() throws Throwable { super.finalize(); this.descFile.delete(); } }
已经经过验证,与代理类生成的数据是一模一样的,解析也没有问题,请放心使用。
严正声明,本博的原创网站是ITeye,如再有转载请注明出处.谢谢合作.