需求:
线上需要一份数据,这份数据是存储在hbase中的(比如对于某一个实体,其各个字段是存储在hbase的各个列的)。而希望最终导出的文件的每一行是一个 protobuf 的message,以方便解析和维护。
具体做法:
定义一个 protobuf message,其各个 field 的名字与 hbase 各列的 key 的名字对应,将 hbase 中各列对应的 value 取出来,填充到对应的 field 中,生成构造完成的message之后, dump 到文件。
利用 java 和 protobuf 的反射功能,将该功能做成通用可配置的,而不是针对每个字段都调用对应的 set 函数。
以下记录碰到的坑。
Class cl = Class.forName(pbClassName); // 首先根据 pb message class 名称得到 Class 对象。(坑1:注意内部类连接符为$,如 package_name.class_name$inner_class_name, 另外注意脚本启动的时候要注意对 $ 转义,否则 shell 会以为 $inner_class_name 为变量)
Method method = cl.getMethod("newBuilder"); // newBuilder 为静态变量,即使没有 message 的具体实例也可以 invoke!yes!
Object obj = method.invoke(null, null);
Message.Builder msgBuilder = (Message.Builder)obj; // 得到 builder
Descriptors.Descriptor descriptor = msgBuilder.getDescriptorForType(); // 得到 descriptor
以下比较直观了:
for(Map.Entryentry : keyValueMap.entrySet()) { // keyValueMap 为从 hbase 取出的各列 key 和 value
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;
}
另要输出文件,将pb base64 编码,否则直接输出会有换行符,按行解析不方便。
byte[] pbByte = pbMessage.toByteArray(); // 坑3: 将 proto 定义文件生成 java 文件的 protoc 文件的版本要与 java 工程 protobuf 的 jar 版本一致,否则会报错。
pbStr = new String(Base64.encodeBase64(pbByte), "UTF-8");