这里写自定义目录标题

  • 前言
  • Protobuf的any: google.protobuf.Any
    • google.protobuf.Any 也是由 proto 文件定义的
    • google.protobuf.Any 本身也是一个 GeneratedMessageV3
  • 自定义AnyData
  • AnyData 的编码和解析
  • 定义一个将typeUrl和Class映射的lookup工具类
  • 查找指定路径下的类及其内部类
    • 找到一个包下的所有类
    • 将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中
  • 参考
  • 前言
  • Protobuf的any: google.protobuf.Any
    • google.protobuf.Any 也是由 proto 文件定义的
    • google.protobuf.Any 本身也是一个 GeneratedMessageV3
  • 自定义AnyData
  • AnyData 的编码和解析
  • 定义一个将typeUrl和Class映射的lookup工具类
  • 查找指定路径下的类及其内部类
    • 找到一个包下的所有类
    • 将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中
  • 参考

前言

google.protobuf.Any 在某些情况下使用的并不是那么方便,希望有更加方便的设计。从protobuf的源码中,我们很容易地知道,google.protobuf.Any 也是一个 proto 的类罢了,完全可以用自己定义的proto类进行替代。

Protobuf的any: google.protobuf.Any

google.protobuf.Any 也是由 proto 文件定义的

去掉所有的注释,google/protobuf/any.proto 也就只有如下的内容,完全可以自定义一个。

syntax = "proto3";package google.protobuf;option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "github.com/golang/protobuf/ptypes/any";
option java_package = "com.google.protobuf";
option java_outer_classname = "AnyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";message Any {string type_url = 1;bytes value = 2;
}

any.proto 编译之后可以得到一个Message类,而 protobuf 还为any添加了一些必要的方法。我们可以从下面的,any.proto 编译出来的类的源码中可以看出 Any.java 与 其他的Message类有什么不同。

google.protobuf.Any 本身也是一个 GeneratedMessageV3

简单地讲一下Any,Any的源码不是很多,删除GeneratedMessageV3Builder相关的代码,大概还有如下代码:

public  final class Any extends GeneratedMessageV3 implements AnyOrBuilder {// typeUrl_ 会是一个 java.lang.String 值private volatile Object typeUrl_;private ByteString value_;private static String getTypeUrl(String typeUrlPrefix, Descriptors.Descriptor descriptor) {return typeUrlPrefix.endsWith("/")? typeUrlPrefix + descriptor.getFullName(): typeUrlPrefix + "/" + descriptor.getFullName();}public static <T extends com.google.protobuf.Message> Any pack(T message) {return Any.newBuilder().setTypeUrl(getTypeUrl("type.googleapis.com",message.getDescriptorForType())).setValue(message.toByteString()).build();}public static <T extends Message> Any pack(T message, String typeUrlPrefix) {return Any.newBuilder().setTypeUrl(getTypeUrl(typeUrlPrefix,message.getDescriptorForType())).setValue(message.toByteString()).build();}public <T extends Message> boolean is(Class<T> clazz) {T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);return getTypeNameFromTypeUrl(getTypeUrl()).equals(defaultInstance.getDescriptorForType().getFullName());}private volatile Message cachedUnpackValue;@java.lang.SuppressWarnings("unchecked")public <T extends Message> T unpack(Class<T> clazz) throws InvalidProtocolBufferException {if (!is(clazz)) {throw new InvalidProtocolBufferException("Type of the Any message does not match the given class.");}if (cachedUnpackValue != null) {return (T) cachedUnpackValue;}T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);T result = (T) defaultInstance.getParserForType().parseFrom(getValue());cachedUnpackValue = result;return result;}...
}

Any 有两个字段:typeUrl_value_

typeUrl_ 保存的值为 Message类的描述类型,原proto文件的message带上package的值,如any的typeUrl为type.googleapis.com/google.protobuf.Anyvalue_ 为 保存到Any对象中的Message对象的ByteString,通过调用方法toByteString()得到。知道这些信息之后,就可以自己重新定一个了。

自定义AnyData

common/any_data.proto

syntax = "proto3";package donespeak.protobuf;option java_package = "io.gitlab.donespeak.proto.common";
option java_outer_classname = "AnyDataProto";// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto
message AnyData {// 值为 <package>.<messageName>,如 api.donespeak.cn/data.proto.DataTypeProtostring type_url = 1;// 值为 message.toByteString();bytes value = 2;
}

AnyData 的编码和解析

自定义的AnyData只是一个普通的Message类,需要另外实现一个Pack和Unpack的工具类。

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.Descriptors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.gitlab.donespeak.proto.common.AnyDataProto;public class AnyDataPacker {private static final String COMPANY_TYPE_URL_PREFIX = "type.donespeakapi.cn";private final AnyDataProto.AnyData anyData;public AnyDataPacker(AnyDataProto.AnyData anyData) {this.anyData = anyData;}public static <T extends com.google.protobuf.Message> AnyDataProto.AnyData pack(T message) {final String typeUrl = getTypeUrl(message.getDescriptorForType());return AnyDataProto.AnyData.newBuilder().setTypeUrl(typeUrl).setValue(message.toByteString()).build();}public static <T extends Message> AnyDataProto.AnyData pack(T message, String typeUrlPrefix) {String typeUrl = getTypeUrl(typeUrlPrefix, message.getDescriptorForType());return AnyDataProto.AnyData.newBuilder().setTypeUrl(typeUrl).setValue(message.toByteString()).build();}public <T extends Message> boolean is(Class<T> clazz) {T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);return getTypeNameFromTypeUrl(anyData.getTypeUrl()).equals(defaultInstance.getDescriptorForType().getFullName());}private static String getTypeNameFromTypeUrl(String typeUrl) {int pos = typeUrl.lastIndexOf('/');return pos == -1 ? "" : typeUrl.substring(pos + 1);}private volatile Message cachedUnpackValue;public <T extends Message> T unpack(Class<T> clazz) throws InvalidProtocolBufferException {if (!is(clazz)) {throw new InvalidProtocolBufferException("Type of the Any message does not match the given class.");}if (cachedUnpackValue != null) {return (T) cachedUnpackValue;}T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);T result = (T) defaultInstance.getParserForType().parseFrom(anyData.getValue());cachedUnpackValue = result;return result;}private static String getTypeUrl(final Descriptors.Descriptor descriptor) {return getTypeUrl(COMPANY_TYPE_URL_PREFIX, descriptor);}private static String getTypeUrl(String typeUrlPrefix, Descriptors.Descriptor descriptor) {return typeUrlPrefix.endsWith("/")? typeUrlPrefix + descriptor.getFullName(): typeUrlPrefix + "/" + descriptor.getFullName();}
}

很容易可以看出,这个类和google.protobuf.Any中的实现基本是一样的。是的,这个类其实就是直接从Any类中抽取出来的。你也可以将unpack方式设计成static的,这样的话,这个工具类就是一个完全的静态工具类了。而这里保留原来的实现是为了在unpack的时候可以做一个缓存。因为Message类都是不变类,所以这样的策略对于多次unpack会很管用。

定义一个将typeUrl和Class映射的lookup工具类

按照前面的描述,这里独立提供一个解包工具,提供更多的解包方法。该工具类有一个静态的解包方法,无需实例化直接调用。另一个方法则需要借助MessageTypeLookup类。MessageTypeLookup类是一个注册类,保存类Message的Descriptor和Class的映射关系。该类的存在,允许了将所有可能的Message类进行注册,然后进行通用的解包,而无需再设法找到AnyData.value的数据对应的类。

MessageTypeUnpacker.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.gitlab.donespeak.proto.common.AnyDataProto;public class MessageTypeUnpacker {private final MessageTypeLookup messageTypeLookup;public MessageTypeUnpacker(MessageTypeLookup messageTypeLookup) {this.messageTypeLookup = messageTypeLookup;}public Message unpack(AnyDataProto.AnyData anyData) throws InvalidProtocolBufferException {AnyDataPacker anyDataPacker = new AnyDataPacker(anyData);Class<? extends Message> messageClass = messageTypeLookup.lookup(anyData.getTypeUrl());return anyDataPacker.unpack(messageClass);}public static <T extends Message> T unpack(AnyDataProto.AnyData anyData, Class<T> messageClass)throws InvalidProtocolBufferException {AnyDataPacker anyDataPacker = new AnyDataPacker(anyData);return anyDataPacker.unpack(messageClass);}
}

MessageTypeLookup 用于注册typeUrl和Message的Class的映射关系,以方便通过typeUrl查找相应的Class。

MessageTypeLookup.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;import java.util.HashMap;
import java.util.Map;public class MessageTypeLookup {private final Map<String, Class<? extends Message>> TYPE_MESSAGE_CLASS_MAP;private MessageTypeLookup(Map<String, Class<? extends Message>> typeMessageClassMap) {this.TYPE_MESSAGE_CLASS_MAP = typeMessageClassMap;}public Class<? extends Message> lookup(final String typeUrl) {String type = typeUrl;if(type.contains("/")) {type = getTypeUrlSuffix(type);}return TYPE_MESSAGE_CLASS_MAP.get(type);}public static Builder newBuilder() {return new Builder();}private static String getTypeUrlSuffix(String fullTypeUrl) {String[] parts = fullTypeUrl.split("/");return parts[parts.length - 1];}public static class Builder {private final Map<String, Class<? extends Message>> TYPE_MESSAGE_CLASS_BUILDER_MAP;public Builder() {TYPE_MESSAGE_CLASS_BUILDER_MAP = new HashMap<>();}public Builder addMessageTypeMapping(final Descriptors.Descriptor descriptor,final Class<? extends Message> messageClass) {TYPE_MESSAGE_CLASS_BUILDER_MAP.put(descriptor.getFullName(), messageClass);return this;}public MessageTypeLookup build() {return new MessageTypeLookup(TYPE_MESSAGE_CLASS_BUILDER_MAP);}}
}

有了MessageTypeLookup之后,可以将所有可能用到的Message都预先注册到这个类中,再借助该类进行解包这样基本就可以实现一个通用的AnyData的打包解包的实现了。但这个类的注册会非常的麻烦,需要手动将所有的Message都添加进来,费力而且容易出错,以后每次添加新的类还要进行添加,很麻烦。

查找指定路径下的类及其内部类

为了解决上面的MessageTypeLookup的不足,可以添加一个按照包的路径查找符合条件的类的方法。在开发中,一般会将所有的Proto都放在一个统一的包名下,所以只需要知道这个包名,然后扫描这个包下的所有类,找到GeneratedMessageV3的子类。再将得到的结果注册到MessageTypeLookup即可。这样实现之后,即使添加新的Message类,也不需要手动添加到MessageTypeLookup中也可以自动实现注册了。

找到一个包下的所有类

为了实现找到一个包下的所有类,这借助了Reflection库,该库提供了很多有用的反射方法。如果想要自己实现一个这样的反射方法,其实挺麻烦的,而且还会有很多坑。之后有时间再进一步讲解反射和类的加载相关的内容吧,感觉会很有趣。

这部分的灵感是来自于Spring@ComponentScan注解。类似的,这里提供了两种扫描方式,一个是包名前缀,另一是指定类所在的包作为扫描的包。这两种方式均允许提供多个路径。

<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.9.11</version>
</dependency>

ClassScanner.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import java.util.Set;
import com.google.protobuf.GeneratedMessageV3;
import org.reflections.Reflections;public class ClassScanner {public static <T> Set<Class<? extends T>> lookupClasses(Class<T> subType, String... basePackages) {Reflections reflections = new Reflections(basePackages);return reflections.getSubTypesOf(subType);}public static <T> Set<Class<? extends T>> lookupClasses(Class<T> subType, Class<?>... basePackageClasses) {String[] basePackages = new String[basePackageClasses.length];for(int i = 0; i < basePackageClasses.length; i ++) {basePackages[i] = basePackageClasses[i].getPackage().getName();}return lookupClasses(subType, basePackages);}
}

将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中

当我们有了类的扫描工具类之后,“将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中”的需求就变得非常容易了。

有了ClassScanner,我们可以得到所有的GeneratedMessageV3类的类对象,还需要获取typeUrl。因为 Message#getDescriptorForType() 方式是一个对象的方法,所以在得到所需要的类的类对象之后需要用反射的方法得到一个实例,再调用getDescriptorForType()方法以获取typeUrl。又知道Message类都是不可变类,而且所有的构造方法都是私有的,因而只能通过Builder类创建。这里先通过反射调用静态方法Message#newBuilder()创建一个Builder,再通过Builder得到Message实例。到这里,所有需要的工作都完成了。

MessageTypeLookupUtil.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.Message;import java.lang.reflect.InvocationTargetException;
import java.util.Set;public class MessageTypeLookupUtil {public static MessageTypeLookup getMessageTypeLookup(String... messageBasePackages) {// 这里使用 GeneratedMessageV3作为父类查找,防止类似com.google.protobuf.AbstractMessage的类出现Set<Class<? extends GeneratedMessageV3>>klasses = ClassScanner.lookupClasses(GeneratedMessageV3.class, messageBasePackages);return generateMessageTypeLookup(klasses);}private static MessageTypeLookup generateMessageTypeLookup(Set<Class<? extends GeneratedMessageV3>> klasses) {MessageTypeLookup.Builder messageTypeLookupBuilder = MessageTypeLookup.newBuilder();try {for (Class<? extends GeneratedMessageV3> klass : klasses) {Message.Builder builder = (Message.Builder)klass.getMethod("newBuilder").invoke(null);Message messageV3 = builder.build();messageTypeLookupBuilder.addMessageTypeMapping(messageV3.getDescriptorForType(), klass);}} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {// will never happenthrow new RuntimeException(e.getMessage(), e);}return messageTypeLookupBuilder.build();}public static MessageTypeLookup getMessageTypeLookup(Class<?>... messageBasePackageClasses) {// 这里使用 GeneratedMessageV3作为父类查找,防止类似com.google.protobuf.AbstractMessage的类出现Set<Class<? extends GeneratedMessageV3>>klasses = ClassScanner.lookupClasses(GeneratedMessageV3.class, messageBasePackageClasses);return generateMessageTypeLookup(klasses);}
}

参考

  • protocolbuffers/protobuf/src/google/protobuf/any.proto @Github
  • ronmamo/reflections @Github
  • ronmamo/reflections#UseCases.md @Github
  • Protocol Buffers, Part 3 — JSON Format @codeburst

前言

google.protobuf.Any 在某些情况下使用的并不是那么方便,希望有更加方便的设计。从protobuf的源码中,我们很容易地知道,google.protobuf.Any 也是一个 proto 的类罢了,完全可以用自己定义的proto类进行替代。

Protobuf的any: google.protobuf.Any

google.protobuf.Any 也是由 proto 文件定义的

去掉所有的注释,google/protobuf/any.proto 也就只有如下的内容,完全可以自定义一个。

syntax = "proto3";package google.protobuf;option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "github.com/golang/protobuf/ptypes/any";
option java_package = "com.google.protobuf";
option java_outer_classname = "AnyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";message Any {string type_url = 1;bytes value = 2;
}

any.proto 编译之后可以得到一个Message类,而 protobuf 还为any添加了一些必要的方法。我们可以从下面的,any.proto 编译出来的类的源码中可以看出 Any.java 与 其他的Message类有什么不同。

google.protobuf.Any 本身也是一个 GeneratedMessageV3

简单地讲一下Any,Any的源码不是很多,删除GeneratedMessageV3Builder相关的代码,大概还有如下代码:

public  final class Any extends GeneratedMessageV3 implements AnyOrBuilder {// typeUrl_ 会是一个 java.lang.String 值private volatile Object typeUrl_;private ByteString value_;private static String getTypeUrl(String typeUrlPrefix, Descriptors.Descriptor descriptor) {return typeUrlPrefix.endsWith("/")? typeUrlPrefix + descriptor.getFullName(): typeUrlPrefix + "/" + descriptor.getFullName();}public static <T extends com.google.protobuf.Message> Any pack(T message) {return Any.newBuilder().setTypeUrl(getTypeUrl("type.googleapis.com",message.getDescriptorForType())).setValue(message.toByteString()).build();}public static <T extends Message> Any pack(T message, String typeUrlPrefix) {return Any.newBuilder().setTypeUrl(getTypeUrl(typeUrlPrefix,message.getDescriptorForType())).setValue(message.toByteString()).build();}public <T extends Message> boolean is(Class<T> clazz) {T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);return getTypeNameFromTypeUrl(getTypeUrl()).equals(defaultInstance.getDescriptorForType().getFullName());}private volatile Message cachedUnpackValue;@java.lang.SuppressWarnings("unchecked")public <T extends Message> T unpack(Class<T> clazz) throws InvalidProtocolBufferException {if (!is(clazz)) {throw new InvalidProtocolBufferException("Type of the Any message does not match the given class.");}if (cachedUnpackValue != null) {return (T) cachedUnpackValue;}T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);T result = (T) defaultInstance.getParserForType().parseFrom(getValue());cachedUnpackValue = result;return result;}...
}

Any 有两个字段:typeUrl_value_

typeUrl_ 保存的值为 Message类的描述类型,原proto文件的message带上package的值,如any的typeUrl为type.googleapis.com/google.protobuf.Anyvalue_ 为 保存到Any对象中的Message对象的ByteString,通过调用方法toByteString()得到。知道这些信息之后,就可以自己重新定一个了。

自定义AnyData

common/any_data.proto

syntax = "proto3";package donespeak.protobuf;option java_package = "io.gitlab.donespeak.proto.common";
option java_outer_classname = "AnyDataProto";// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto
message AnyData {// 值为 <package>.<messageName>,如 api.donespeak.cn/data.proto.DataTypeProtostring type_url = 1;// 值为 message.toByteString();bytes value = 2;
}

AnyData 的编码和解析

自定义的AnyData只是一个普通的Message类,需要另外实现一个Pack和Unpack的工具类。

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.Descriptors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.gitlab.donespeak.proto.common.AnyDataProto;public class AnyDataPacker {private static final String COMPANY_TYPE_URL_PREFIX = "type.donespeakapi.cn";private final AnyDataProto.AnyData anyData;public AnyDataPacker(AnyDataProto.AnyData anyData) {this.anyData = anyData;}public static <T extends com.google.protobuf.Message> AnyDataProto.AnyData pack(T message) {final String typeUrl = getTypeUrl(message.getDescriptorForType());return AnyDataProto.AnyData.newBuilder().setTypeUrl(typeUrl).setValue(message.toByteString()).build();}public static <T extends Message> AnyDataProto.AnyData pack(T message, String typeUrlPrefix) {String typeUrl = getTypeUrl(typeUrlPrefix, message.getDescriptorForType());return AnyDataProto.AnyData.newBuilder().setTypeUrl(typeUrl).setValue(message.toByteString()).build();}public <T extends Message> boolean is(Class<T> clazz) {T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);return getTypeNameFromTypeUrl(anyData.getTypeUrl()).equals(defaultInstance.getDescriptorForType().getFullName());}private static String getTypeNameFromTypeUrl(String typeUrl) {int pos = typeUrl.lastIndexOf('/');return pos == -1 ? "" : typeUrl.substring(pos + 1);}private volatile Message cachedUnpackValue;public <T extends Message> T unpack(Class<T> clazz) throws InvalidProtocolBufferException {if (!is(clazz)) {throw new InvalidProtocolBufferException("Type of the Any message does not match the given class.");}if (cachedUnpackValue != null) {return (T) cachedUnpackValue;}T defaultInstance = com.google.protobuf.Internal.getDefaultInstance(clazz);T result = (T) defaultInstance.getParserForType().parseFrom(anyData.getValue());cachedUnpackValue = result;return result;}private static String getTypeUrl(final Descriptors.Descriptor descriptor) {return getTypeUrl(COMPANY_TYPE_URL_PREFIX, descriptor);}private static String getTypeUrl(String typeUrlPrefix, Descriptors.Descriptor descriptor) {return typeUrlPrefix.endsWith("/")? typeUrlPrefix + descriptor.getFullName(): typeUrlPrefix + "/" + descriptor.getFullName();}
}

很容易可以看出,这个类和google.protobuf.Any中的实现基本是一样的。是的,这个类其实就是直接从Any类中抽取出来的。你也可以将unpack方式设计成static的,这样的话,这个工具类就是一个完全的静态工具类了。而这里保留原来的实现是为了在unpack的时候可以做一个缓存。因为Message类都是不变类,所以这样的策略对于多次unpack会很管用。

定义一个将typeUrl和Class映射的lookup工具类

按照前面的描述,这里独立提供一个解包工具,提供更多的解包方法。该工具类有一个静态的解包方法,无需实例化直接调用。另一个方法则需要借助MessageTypeLookup类。MessageTypeLookup类是一个注册类,保存类Message的Descriptor和Class的映射关系。该类的存在,允许了将所有可能的Message类进行注册,然后进行通用的解包,而无需再设法找到AnyData.value的数据对应的类。

MessageTypeUnpacker.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.gitlab.donespeak.proto.common.AnyDataProto;public class MessageTypeUnpacker {private final MessageTypeLookup messageTypeLookup;public MessageTypeUnpacker(MessageTypeLookup messageTypeLookup) {this.messageTypeLookup = messageTypeLookup;}public Message unpack(AnyDataProto.AnyData anyData) throws InvalidProtocolBufferException {AnyDataPacker anyDataPacker = new AnyDataPacker(anyData);Class<? extends Message> messageClass = messageTypeLookup.lookup(anyData.getTypeUrl());return anyDataPacker.unpack(messageClass);}public static <T extends Message> T unpack(AnyDataProto.AnyData anyData, Class<T> messageClass)throws InvalidProtocolBufferException {AnyDataPacker anyDataPacker = new AnyDataPacker(anyData);return anyDataPacker.unpack(messageClass);}
}

MessageTypeLookup 用于注册typeUrl和Message的Class的映射关系,以方便通过typeUrl查找相应的Class。

MessageTypeLookup.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;import java.util.HashMap;
import java.util.Map;public class MessageTypeLookup {private final Map<String, Class<? extends Message>> TYPE_MESSAGE_CLASS_MAP;private MessageTypeLookup(Map<String, Class<? extends Message>> typeMessageClassMap) {this.TYPE_MESSAGE_CLASS_MAP = typeMessageClassMap;}public Class<? extends Message> lookup(final String typeUrl) {String type = typeUrl;if(type.contains("/")) {type = getTypeUrlSuffix(type);}return TYPE_MESSAGE_CLASS_MAP.get(type);}public static Builder newBuilder() {return new Builder();}private static String getTypeUrlSuffix(String fullTypeUrl) {String[] parts = fullTypeUrl.split("/");return parts[parts.length - 1];}public static class Builder {private final Map<String, Class<? extends Message>> TYPE_MESSAGE_CLASS_BUILDER_MAP;public Builder() {TYPE_MESSAGE_CLASS_BUILDER_MAP = new HashMap<>();}public Builder addMessageTypeMapping(final Descriptors.Descriptor descriptor,final Class<? extends Message> messageClass) {TYPE_MESSAGE_CLASS_BUILDER_MAP.put(descriptor.getFullName(), messageClass);return this;}public MessageTypeLookup build() {return new MessageTypeLookup(TYPE_MESSAGE_CLASS_BUILDER_MAP);}}
}

有了MessageTypeLookup之后,可以将所有可能用到的Message都预先注册到这个类中,再借助该类进行解包这样基本就可以实现一个通用的AnyData的打包解包的实现了。但这个类的注册会非常的麻烦,需要手动将所有的Message都添加进来,费力而且容易出错,以后每次添加新的类还要进行添加,很麻烦。

查找指定路径下的类及其内部类

为了解决上面的MessageTypeLookup的不足,可以添加一个按照包的路径查找符合条件的类的方法。在开发中,一般会将所有的Proto都放在一个统一的包名下,所以只需要知道这个包名,然后扫描这个包下的所有类,找到GeneratedMessageV3的子类。再将得到的结果注册到MessageTypeLookup即可。这样实现之后,即使添加新的Message类,也不需要手动添加到MessageTypeLookup中也可以自动实现注册了。

找到一个包下的所有类

为了实现找到一个包下的所有类,这借助了Reflection库,该库提供了很多有用的反射方法。如果想要自己实现一个这样的反射方法,其实挺麻烦的,而且还会有很多坑。之后有时间再进一步讲解反射和类的加载相关的内容吧,感觉会很有趣。

这部分的灵感是来自于Spring@ComponentScan注解。类似的,这里提供了两种扫描方式,一个是包名前缀,另一是指定类所在的包作为扫描的包。这两种方式均允许提供多个路径。

<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.9.11</version>
</dependency>

ClassScanner.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import java.util.Set;
import com.google.protobuf.GeneratedMessageV3;
import org.reflections.Reflections;public class ClassScanner {public static <T> Set<Class<? extends T>> lookupClasses(Class<T> subType, String... basePackages) {Reflections reflections = new Reflections(basePackages);return reflections.getSubTypesOf(subType);}public static <T> Set<Class<? extends T>> lookupClasses(Class<T> subType, Class<?>... basePackageClasses) {String[] basePackages = new String[basePackageClasses.length];for(int i = 0; i < basePackageClasses.length; i ++) {basePackages[i] = basePackageClasses[i].getPackage().getName();}return lookupClasses(subType, basePackages);}
}

将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中

当我们有了类的扫描工具类之后,“将一个包下的GeneratedMessageV3的子类注册到MessageTypeLookup中”的需求就变得非常容易了。

有了ClassScanner,我们可以得到所有的GeneratedMessageV3类的类对象,还需要获取typeUrl。因为 Message#getDescriptorForType() 方式是一个对象的方法,所以在得到所需要的类的类对象之后需要用反射的方法得到一个实例,再调用getDescriptorForType()方法以获取typeUrl。又知道Message类都是不可变类,而且所有的构造方法都是私有的,因而只能通过Builder类创建。这里先通过反射调用静态方法Message#newBuilder()创建一个Builder,再通过Builder得到Message实例。到这里,所有需要的工作都完成了。

MessageTypeLookupUtil.java

package io.gitlab.donespeak.javatool.toolprotobuf.anydata;import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.Message;import java.lang.reflect.InvocationTargetException;
import java.util.Set;public class MessageTypeLookupUtil {public static MessageTypeLookup getMessageTypeLookup(String... messageBasePackages) {// 这里使用 GeneratedMessageV3作为父类查找,防止类似com.google.protobuf.AbstractMessage的类出现Set<Class<? extends GeneratedMessageV3>>klasses = ClassScanner.lookupClasses(GeneratedMessageV3.class, messageBasePackages);return generateMessageTypeLookup(klasses);}private static MessageTypeLookup generateMessageTypeLookup(Set<Class<? extends GeneratedMessageV3>> klasses) {MessageTypeLookup.Builder messageTypeLookupBuilder = MessageTypeLookup.newBuilder();try {for (Class<? extends GeneratedMessageV3> klass : klasses) {Message.Builder builder = (Message.Builder)klass.getMethod("newBuilder").invoke(null);Message messageV3 = builder.build();messageTypeLookupBuilder.addMessageTypeMapping(messageV3.getDescriptorForType(), klass);}} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {// will never happenthrow new RuntimeException(e.getMessage(), e);}return messageTypeLookupBuilder.build();}public static MessageTypeLookup getMessageTypeLookup(Class<?>... messageBasePackageClasses) {// 这里使用 GeneratedMessageV3作为父类查找,防止类似com.google.protobuf.AbstractMessage的类出现Set<Class<? extends GeneratedMessageV3>>klasses = ClassScanner.lookupClasses(GeneratedMessageV3.class, messageBasePackageClasses);return generateMessageTypeLookup(klasses);}
}

参考

  • protocolbuffers/protobuf/src/google/protobuf/any.proto @Github
  • ronmamo/reflections @Github
  • ronmamo/reflections#UseCases.md @Github
  • Protocol Buffers, Part 3 — JSON Format @codeburst

实现自己的Protobuf Any相关推荐

  1. 使用Protobuf文件一键生成Java类

    使用Protobuf文件生成Java类 .proto 文件生成 .java 参考 看了一篇文章:主题是 proto 先生成 desc,然后在用 FreeMarker 模板引擎来做代码自动生成了: .p ...

  2. 简单protobuf

    protobuf的数据类型,有最简单的那种数据类型,就是一个文件中,定义了一个message 可以在一个文件中定义两个message,两个message之间是没有关联的 可以在一个文件中,定义两个me ...

  3. google ProtoBuf开发者指南

    目录 1   概览 1.1   什么是protocol buffer 1.2   他们如何工作 1.3   为什么不用XML? 1.4   听起来像是为我的解决方案,如何开始? 1.5   一点历史 ...

  4. Ubuntu14.04上编译指定版本的protobuf源码操作步骤

    Google Protobuf的介绍可以参考 http://blog.csdn.net/fengbingchun/article/details/49977903 ,这里介绍在Ubuntu14.04上 ...

  5. Windows7上配置Python Protobuf 操作步骤

    1.  按照http://blog.csdn.net/fengbingchun/article/details/8183468 中步骤,首先安装Python 2.7.10: 2.  按照http:// ...

  6. 【C++】Google Protocol Buffer(protobuf)详解(二)

    代码走读:caffe中protobuf的详细使用过程 [一]proto文件,以caffe.proto中BlobShape为例 syntax = "proto2"; //指明prot ...

  7. 【C++】Google Protocol Buffer(protobuf)详解(一)

    1.简介 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准, Protocol Buffers 是一种轻便高效的结构化数据存储格式 ...

  8. protobufjs 命令执行_【原码笔记】-- protobuf.js 与 Long.js

    protobuf.js的结构和webpack的加载之后的结构很相似.这样的模块化组合是个不错的结构方式.1个是适应了不同的加载方式,2个模块直接很独立.webpack的功能更全一点.但如果自己封装js ...

  9. 在网络通讯中应用Protobuf

    Protobuf的设计非常适用于在网络通讯中的数据载体,它序列化出来的数据量少再加上以K-V的方式来存储数据,对消息的版本兼容性非常强:还有一个比较大的优点就是有着很多的语言平台支持.下面讲解一下如何 ...

  10. 连信的protobuf数据格式

    点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 连信里用到的protobuf结构." 在看本文之前,可以先进行一下回顾,之前已经对协议的框架进行了整体的介绍: 连信协议整体框架 ...

最新文章

  1. Codeforces 1338 题解
  2. 从Java中的length和length()开始
  3. 简单试用了一下 dynamips 7200路由模拟器
  4. 微信小程序css3动画怎么写,微信小程序动画课程-通过wxss(css)来实现-animation 属性...
  5. 为什么awt_为AWT的机器人创建DSL
  6. nvidia-smi 命令详解
  7. ios 筛选_万千网友让quot;低调使用quot;的软件!居然还支持iOS
  8. 解决VS2017引用报错问题
  9. 高性能服务器机柜,TS系列网络服务器机柜
  10. [Study Notes][001][gstreamer] memory
  11. 一文理解设计模式--单例模式(Singleton)
  12. 俞渝欲花费百万召开“抢章座谈会” ?当当网回应来了
  13. 链表相关的面试题型总结
  14. c语言随机生成算式的对错判断,蔡奇宏软件工程第二次作业--四则运算
  15. 自己写的python脚本(抄的别人的,自己改了改,用于整理大量txt数据并插入到数据库)...
  16. 深入解剖 linux内存管理之mmap
  17. wsl使用ssh连接
  18. Setup Factory安装结束自动启动程序
  19. python写出租车计费系统_用VHDL设计出租车计费系统
  20. sqlserver数据库18456错误怎么解决?

热门文章

  1. SAP开发-同时定义同名的工作区、内表 OCCURS 0 WITH HEADER LINE
  2. 然而大部分工程师的期权并没有什么用
  3. python读取fits第三方库_Python读取和显示Fits文件
  4. phpword 实现word文件模板字符替换
  5. js 用 querySelectorAll 提取文本格再式化输出
  6. 《Excel高手捷径:一招鲜,吃遍天》一第29招 Excel 文件“减肥瘦身”秘诀
  7. 火车头采集器计划任务设置时间间隔无效问题解决
  8. “沉浸式”住宿体验——酒店的新瓶,民宿的老酒
  9. 修改域名指向的服务器,让域名指向服务器
  10. oracle. 设置参数 sid,更改Oracle数据库的SID