1. 程式人生 > >ProtoBuf 反射詳解

ProtoBuf 反射詳解

最初是起源於這樣一個問題:

給定一個pb物件,如何自動遍歷該物件的所有欄位?

即是否有一個通用的方法可以遍歷任意pb物件的所有欄位,而不用關心具體物件型別。

使用場景上有很多:

比如json格式字串的相互轉換,或者bigtable里根據pb物件的欄位自動寫列名和對應的value。

例如定義了pb messge型別Person如下:

123 Person
person;person.set_name("yingshin");person.set_age(21);

能否將該物件自動轉化為json字串{"name":"yingshin","age":21},或者自動的寫hbase裡的多列:

key column-name column-value
uid name yingshin
uid age 21

如果設定了新的欄位,比如person.set_email("[email protected]"),則自動新增新的一列:

key column-name column-value
uid email [email protected]

答案就是 pb的反射功能

我們的目標是提供這樣兩個介面:

12345 //從給定的message物件序列化為固定格式的字串voidserialize_message(constgoogle::protobuf::Message&message,std::string*serialized_string);//從給定的字串按照固定格式還原為原message物件voidparse_message(conststd::string&serialized_string,google::protobuf::Message*message);

接下來逐步介紹下如何實現。

1. 反射相關介面

要介紹pb的反射功能,先看一個相關的UML示例圖:

pb-reflection

各個類以及介面說明:

1.1 Message

Person是自定義的pb型別,繼承自Message. MessageLite作為Message基類,更加輕量級一些。

通過Message的兩個介面GetDescriptor/GetReflection,可以獲取該型別對應的Descriptor/Reflection。

1.2 Descriptor

Descriptor是對message型別定義的描述,包括message的名字、所有欄位的描述、原始的proto檔案內容等。

本文中我們主要關注跟欄位描述相關的介面,例如:

  1. 獲取所有欄位的個數:int field_count() const
  2. 獲取單個欄位描述型別FieldDescriptor的介面有很多個,例如
123 constFieldDescriptor*field(intindex)const;//根據定義順序索引獲取constFieldDescriptor*FindFieldByNumber(intnumber)const;//根據tag值獲取constFieldDescriptor*FindFieldByName(conststring&name)const;//根據field name獲取
1.3 FieldDescriptor

FieldDescriptor描述message中的單個欄位,例如欄位名,欄位屬性(optional/required/repeated)等。

對於proto定義裡的每種型別,都有一種對應的C++型別,例如:

123 enumCppType{CPPTYPE_INT32=1,//TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32}

獲取型別的label屬性:

1234567 enumLabel{LABEL_OPTIONAL=1,//optionalLABEL_REQUIRED=2,//requiredLABEL_REPEATED=3,//repeatedMAX_LABEL=3,//Constant useful for defining lookup tables indexed by Label.}

獲取欄位的名稱:const string& name() const;

1.4 Reflection

Reflection主要提供了動態讀寫pb欄位的介面,對pb物件的自動讀寫主要通過該類完成。

對每種型別,Reflection都提供了一個單獨的介面用於讀寫欄位對應的值。

例如對於讀操作:

1234 virtual int32  GetInt32(constMessage&message,constFieldDescriptor*field)const=0;virtual int64  GetInt64(constMessage&message,constFieldDescriptor*field)const=0;

特殊的,對於列舉和巢狀的message:

123456 virtual constEnumValueDescriptor*GetEnum(constMessage&message,constFieldDescriptor*field)const=0;// See MutableMessage() for the meaning of the "factory" parameter.virtual constMessage&GetMessage(constMessage&message,constFieldDescriptor*field,MessageFactory*factory=NULL)const=0;

對於寫操作也是類似的介面,例如SetInt32/SetInt64/SetEnum等。

2. 反射示例

示例主要是接收任意型別的message物件,遍歷解析其中的每個欄位、以及對應的值,按照自定義的格式儲存到一個string中。同時重新反序列化該string,讀取欄位以及value,填充到message物件中。例如:

其中Person是自定義的protobuf message型別,用於設定一些欄位驗證我們的程式。

單純的序列化/反序列化功能可以通過pb自帶的SerializeToString/ParseFromString介面完成。這裡主要是為了同時展示自動從pb物件裡提取field/value,自動根據field/value來還原pb物件這個功能。

123456789101112131415161718192021 intmain(){std::stringserialized_string;{tutorial::Person person;person.set_name("yingshin");person.set_id(123456789);person.set_email("[email protected]");::tutorial::Person_PhoneNumber*phone=person.mutable_phone();phone->set_type(tutorial::Person::WORK);phone->set_number("13266666666");serialize_message(person,&serialized_string);}{tutorial::Person person;parse_message(serialized_string,&person);}return0;}

其中Person定義是對example裡的addressbook.proto做了少許修改(修改的原因是本文沒有涉及pb裡陣列的處理)

1234567891011121314151617181920 packagetutorial;messagePerson{required stringname=1;required int32 id=2;// Unique ID number for this person.optional stringemail=3;enumPhoneType{MOBILE=0;HOME=1;WORK=2;}messagePhoneNumber{required stringnumber=1;optional PhoneType type=2[default=HOME];}optional PhoneNumber phone=4;}

3. 反射例項實現

3.1 serialize_message

serialize_message遍歷提取message中各個欄位以及對應的值,序列化到string中。主要思路就是通過Descriptor得到每個欄位的描述符:欄位名、欄位的cpp型別。通過Reflection的GetX介面獲取對應的value。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 voidserialize_message(constgoogle::protobuf::Message&message,std::string*serialized_string){constgoogle::protobuf::Descriptor*descriptor=message.GetDescriptor();constgoogle::protobuf::Reflection*reflection=message.GetReflection();for(inti=0;ifield_count();++i){constgoogle::protobuf::FieldDescriptor*field=descriptor->field(i);boolhas_field=reflection->HasField(message,field);if(has_field){//arrays not supportedassert(!field->is_repeated());switch(field->cpp_type()){#define CASE_FIELD_TYPE(cpptype, method, valuetype)casegoogle::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{valuetype value=reflection->Get##method(message, field);intwsize=field->name().size();serialized_string->append(reinterpret_cast(&wsize),sizeof(wsize));serialized_string->append(field->name().c_str(),field->name().size());wsize=sizeof(value);serialized_string->append(reinterpret_cast(&wsize),sizeof(wsize));serialized_string->append(reinterpret_cast(&value),sizeof(value));break;}CASE_FIELD_TYPE(INT32,Int32,int);CASE_FIELD_TYPE(UINT32,UInt32,uint32_t);CASE_FIELD_TYPE(FLOAT,Float,float);CASE_FIELD_TYPE(DOUBLE,Double,double);CASE_FIELD_TYPE(BOOL,Bool,bool);CASE_FIELD_TYPE(INT64,Int64,int64_t);CASE_FIELD_TYPE(UINT64,UInt64,uint64_t);#undef CASE_FIELD_TYPEcasegoogle::protobuf::FieldDescriptor