前文(https://blog.csdn.net/liuxiao723846/article/details/96613960)介绍了如何使用动态的方式将消费者解放,生产者是否也能动态的解放呢?查阅了资料C++版本支持反射的方式构建消息(api的方式生成C++原文件),java版本对应的api没有找到,但是吧中间的过程记录一下。
先看下代码:
public static void generateMsg() {
// 构造
com.google.protobuf.DescriptorProtos.FileDescriptorProto.Builder fileDesProtoBuilder = FileDescriptorProto
.newBuilder();
fileDesProtoBuilder.setSyntax("proto3");
fileDesProtoBuilder.setName("food.proto");// proto文件
Builder fileOptionBuilder = FileOptions.newBuilder();
fileOptionBuilder.setJavaPackage("com.lanjingling.pair");
fileOptionBuilder.setJavaOuterClassname("Food");
fileDesProtoBuilder.setOptions(fileOptionBuilder.build());
com.google.protobuf.DescriptorProtos.DescriptorProto.Builder addMessageTypeBuilder = fileDesProtoBuilder
.addMessageTypeBuilder();
addMessageTypeBuilder.setName("Pair");// message name
com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Builder fidldDesProtoBuilder = FieldDescriptorProto
.newBuilder();
fidldDesProtoBuilder.setName("key");
fidldDesProtoBuilder.setTypeName("string");
fidldDesProtoBuilder.setNumber(1);
addMessageTypeBuilder.addField(fidldDesProtoBuilder.build());
com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Builder fidldDesProtoBuilder1 = FieldDescriptorProto
.newBuilder();
fidldDesProtoBuilder1.setName("value");
fidldDesProtoBuilder1.setTypeName("int32");
fidldDesProtoBuilder1.setNumber(2);
addMessageTypeBuilder.addField(fidldDesProtoBuilder1.build());
FileDescriptorProto build = fileDesProtoBuilder.build();
// 创建
com.google.protobuf.DynamicMessage.Builder newBuilder = DynamicMessage
.newBuilder(build);
DynamicMessage build2 = newBuilder.build();
System.out.println(build2);
// TODO 上面的方式无法将消息生成对应的java类
// 可以使用protoc命令生成java类,然后使用class load将类加载到jvm
// 反射
try {
Class cl = Class.forName("com.lanjingling.pair.Food.$Pair");
Method method = cl.getMethod("newBuilder");
Object obj = method.invoke(null, new Object[] {});
Message.Builder msgBuilder = (Message.Builder) obj;
buildMessage(msgBuilder,new HashMap<>());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void buildMessage(Message.Builder msgBuilder,Map<String, String> map) {
Descriptors.Descriptor descriptor = msgBuilder.getDescriptorForType();
for (Map.Entry<String, String> entry : map.entrySet()) {
Descriptors.FieldDescriptor filedDescriptor = descriptor.findFieldByName(entry.getKey());
if (filedDescriptor == null) {
continue;
}
boolean isRepeated = filedDescriptor.isRepeated();
Descriptors.FieldDescriptor.JavaType type = filedDescriptor.getJavaType();
if (isRepeated) {
String value = entry.getValue();
String[] strArray = value.split(",");
for (int i = 0; i < strArray.length; ++i) {
Object valueObject = getObject(strArray[i], type); // getObject
if (valueObject == null) {
continue;
}
msgBuilder.addRepeatedField(filedDescriptor, valueObject);
}
} else {
Object valueObject = getObject(entry.getValue(), type);
if (valueObject == null) {
continue;
}
msgBuilder.setField(filedDescriptor,
getObject(entry.getValue(), type));
}
}
Message msg = msgBuilder.build();
}
private static Object getObject(String rawString,
Descriptors.FieldDescriptor.JavaType type) {
try {
switch (type) {
case INT:
return Integer.valueOf(rawString);
case LONG:
return Long.valueOf(rawString);
case FLOAT:
return Float.valueOf(rawString);
case DOUBLE:
return Double.valueOf(rawString);
case BOOLEAN:
return Boolean.valueOf(rawString);
case STRING:
return rawString;
default:
// BYTE_STRING, ENUM, MESSAGE 哈哈先支持以上这些啦
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
代码流程:
1)利用FileDescriptorProto、FieldDescriptorProto等类,来构造一个proto消息;构造完打印结果如下:
name: "food.proto"
message_type {
name: "Pair"
field {
name: "key"
number: 1
type_name: "string"
}
field {
name: "value"
number: 2
type_name: "int32"
}
}
options {
java_package: "com.lanjingling.pair"
java_outer_classname: "Food"
}
syntax: "proto3"
2)接下来,想找一个api将上面的proto消息动态编译,生成java源文件,然后加载类到jvm中。可惜java版本的protobuf没有找到这样的api(C++中有),那么这里只能是先将proto结构保存到文件中,然后用命令行的方式生成,然后加载了,代码如下:
String protoCMD="protoc --java_out=./ ./food.ptoro"
Process process = Runtime.getRuntime().exec(protocCMD);
//加载到jvm
3)通过java反射的方式加载对应的proto消息类,然后对其进行写入值。。。在写入值得时候,可以根据类型等进行判断
参考: