Protocol Buffer Java应用实例
生成目標語言代碼
?? ?? 下面的命令幫助我們將MyMessage.proto文件中定義的一組Protocol Buffer格式的消息編譯成目標語言(Java)的代碼。至于消息的內容,我們會在后面以分段的形式逐一列出,同時也會在附件中給出所有源代碼。?? ???protoc?-I=./message --java_out=./src ./MyMessage.proto
? ? ? 從上面的命令行參數中可以看出,待編譯的文件為MyMessage.proto,他存放在當前目錄的message子目錄下。--java_out參數則指示編譯工具我們需要生成目標語言是java,輸出目錄是當前目錄的src子目錄。這里需要補充說明的是,因為在MyMessage.proto文件中定義了option java_package = "com.lsk.lyphone"的文件級選項,所以輸出的目前是src/com/lsk/lyphone,生成的目標代碼文件名是MyMessage.java。
簡單message生成的Java代碼
? ? ? 這里先定義一個最簡單的message,其中只是包含原始類型的字段。?? ?? option java_package = "com.lsk.lyphone";
?? ?? option java_outer_classname = "LYPhoneMessage";
?? ?? option optimize_for = LITE_RUNTIME;
?? ?? message LogonReqMessage {
?? ??? ?? required int64 acctID = 1;
?? ?? ? ? required string passwd = 2;
?? ?? }
?? ?? 對于選項java_package和java_outer_classname的功能,我們已經在之前的一篇Blog(語言規范)中進行了清晰的闡述,這里就不在另行介紹了。然而對于選項optimize_for,這里將結合本例給出一些實用性描述。
?? ?? 當前.proto文件中該選項的值為LITE_RUNTIME,因此由該.proto文件生成的所有Java類的父類均為com.google.protobuf.GeneratedMessageLite,而非com.google.protobuf.GeneratedMessage,同時與之對應的Builder類則均繼承自com.google.protobuf.MessageLiteOrBuilder,而非com.google.protobuf.MessageOrBuilder。在之前的博客中已經給出了一些簡要的說明,MessageLite接口是Message的父接口,在MessageLite中將缺少Protocol Buffer對反射的支持,而此功能均在Message接口中提供了接口規范,同時又在其實現類GeneratedMessage中給予了最小功能的實現。對于我們的項目而言,整個系統相對比較封閉,不會和更多的外部程序進行交互,與此同時,我們的客戶端部分又是運行在Android平臺,有鑒于此,我們考慮使用LITE版本的Protocol Buffer。這樣不僅可以得到更高編碼效率,而且生成代碼編譯后所占用的資源也會更少,至于反射所能帶來的靈活性和極易擴展性,對于該項目而言完全可以忽略。下面我們來看一下由message LogonReqMessage生成的Java類的部分聲明,以及常用方法的說明性注釋。
? ? ? 在做各種case的對比性分析之前必須要事先聲明的是,Protocol Buffer針對Java語言所生成的代碼和C++相比存在一個非常重要的差別,即為每個消息均會生成一個Builder接口和一個與消息對應的實現類,該實現類又將同時實現生成的Builder接口和擴展Protocol Buffer內置的GeneratedMessageLite(或GeneratedMessage)類。這一點對于Protocol Buffer而言,是巧妙的使用了設計模式中的Builder模式。換言之,對于所有消息字段的修改操作均需要通過與其對應的Builder接口輔助完成。相信我們會通過對下面用例的學習可以得到更為清楚的認識。 1 //用于修改LogonReqMessage消息字段的輔助Builder接口。2 //該接口會為消息中的每個字段均提供getter和setter方法。3 public interface LogonReqMessageOrBuilder4 extends com.google.protobuf.MessageLiteOrBuilder {5 6 // required int64 acctID = 1;7 boolean hasAcctID();8 long getAcctID();9 10 // required string passwd = 2;11 boolean hasPasswd();12 String getPasswd();13 }14 //該類為final類,即不可以在被子類化了。這一點在Protocol Buffer的官方文檔中給予了明確15 //的說明,因為子類化將會破壞序列化和反序列化的過程。16 public static final class LogonReqMessage extends17 com.google.protobuf.GeneratedMessageLite18 implements LogonReqMessageOrBuilder {19 20 // Use LogonReqMessage.newBuilder() to construct.21 // 由于所有構造函數均為私有方法,由此可見,我們不能直接new LogonReqMessage的對象22 // 實例,而是只能通過與其對應Builder來構造,或是直接通過反序列化的方式生成。23 private LogonReqMessage(Builder builder) {24 super(builder);25 }26 //該靜態方法為該類Builder接口的工廠方法。返回的Builder實現類在完成各個字段的27 //初始化后,通過build()方法返回與其對應的消息實現類,即LogonReqMessage。28 public static Builder newBuilder() { return Builder.create(); }29 //通過該類的對象獲取與其對應的Builder類對象,一般用于通過Builder類完成消息字段的修改。30 public Builder toBuilder() { return newBuilder(this); }31 32 private LogonReqMessage(boolean noInit) {}33 //判斷當前對象的所有字段是否都已經被初始化。34 public final boolean isInitialized() {35 ... ...36 }37 //獲取已經被初始化后的對象序列化時所占用的字節空間。38 public int getSerializedSize() {39 ... ...40 }41 //從內存中飯序列化LogonReqMessage對象。42 //Protocol Buffer中還提供其他一些接口方法,用于從不同的數據源反序列化對象。43 public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(byte[] data)44 throws com.google.protobuf.InvalidProtocolBufferException {45 return newBuilder().mergeFrom(data).buildParsed();46 }47 //功能和上一個函數相同,只是輸入源改為InputStream接口。48 public static com.lsk.lyphone.LYPhoneMessage.LogonReqMessage parseFrom(java.io.InputStream input)49 throws java.io.IOException {50 return newBuilder().mergeFrom(input).buildParsed();51 }52 53 // required int64 acctID = 1;54 // 下面的靜態變量對應于該字段在.proto中定義的標簽號。該變量的命名規則為:字段(全部大寫) + _FIELD_NUMBER。55 public static final int ACCTID_FIELD_NUMBER = 1;56 public boolean hasAcctID() {57 return ((bitField0_ & 0x00000001) == 0x00000001);58 }59 public long getAcctID() {60 return acctID_;61 }62 63 // required string passwd = 2;64 public static final int PASSWD_FIELD_NUMBER = 2;65 public boolean hasPasswd() {66 return ((bitField0_ & 0x00000002) == 0x00000002);67 }68 public String getPasswd() {69 ... ... 70 }71 //每一個Message類都會包含一個靜態內部類,即與之對應的Builder類。上面代碼中所涉及的Builder類均為該內部類。72 public static final class Builder extends73 com.google.protobuf.GeneratedMessageLite.Builder<74 com.lsk.lyphone.LYPhoneMessage.LogonReqMessage, Builder>75 implements com.lsk.lyphone.LYPhoneMessage.LogonReqMessageOrBuilder {76 //清空當前對象中的所有設置。調用該函數之后,本例中的hasAcctID和hasPasswd都會返回false。 77 public Builder clear() {78 super.clear();79 acctID_ = 0L;80 bitField0_ = (bitField0_ & ~0x00000001);81 passwd_ = "";82 bitField0_ = (bitField0_ & ~0x00000002);83 return this;84 }85 //克隆出一個Builder對象。86 public Builder clone() {87 return create().mergeFrom(buildPartial());88 }89 public com.lsk.lyphone.LYPhoneMessage.LogonReqMessage build() {90 com.lsk.lyphone.LYPhoneMessage.LogonReqMessage result = buildPartial();91 if (!result.isInitialized()) {92 throw newUninitializedMessageException(result);93 }94 return result;95 }96 // Builder類中修改外部消息類的方法。97 // required int64 acctID = 1;98 public boolean hasAcctID() {99 return ((bitField0_ & 0x00000001) == 0x00000001); 100 } 101 public long getAcctID() { 102 return acctID_; 103 } 104 //設置AcctID字段,該函數調用后hasAcctID函數將返回true。 105 //這里之所以讓返回值為Builder對象,就是可以讓調用者在一條代碼中方便的連續修改多個字段, 106 //如:myMessage.setAcctID(100).setPasswd("MyName"); 107 public Builder setAcctID(long value) { 108 bitField0_ |= 0x00000001; 109 acctID_ = value; 110 return this; 111 } 112 //清空AcctID字段,該函數調用后hasAcctID函數返回false。 113 //這里之所以讓返回值為Builder對象,就是可以讓調用者在一條代碼中方便的連續清空多個字段, 114 //如:myMessage.clearAcctID().clearPasswd(); 115 public Builder clearAcctID() { 116 bitField0_ = (bitField0_ & ~0x00000001); 117 acctID_ = 0L; 118 return this; 119 } 120 121 // required string passwd = 2; 122 public boolean hasPasswd() { 123 return ((bitField0_ & 0x00000002) == 0x00000002); 124 } 125 public String getPasswd() { 126 ... ... 127 } 128 public Builder setPasswd(String value) { 129 ... ... 130 } 131 public Builder clearPasswd() { 132 bitField0_ = (bitField0_ & ~0x00000002); 133 passwd_ = getDefaultInstance().getPasswd(); 134 return this; 135 } 136 void setPasswd(com.google.protobuf.ByteString value) { 137 bitField0_ |= 0x00000002; 138 passwd_ = value; 139 } 140 } 141 }
?? ?? 在上面生成的代碼中并沒有列出與序列化相關的函數,這部分代碼基本都是在父類中實現的,我們將在下面的例子中給出一些最基本的用法,有興趣的開發者可以直接看Protocol Buffer中的源碼,這部分代碼比較通俗易懂。
? ? ? 下面是讀寫LogonReqMessage對象的Java測試代碼和說明性注釋。
嵌套message生成的Java代碼
? ? ? enum UserStatus {?? ?? ??? OFFLINE = 0;
?? ? ?? ? ONLINE = 1;
?? ?? }
? ? ? enum LoginResult {
?? ? ?? ? LOGON_RESULT_SUCCESS = 0;
?? ? ? ?? LOGON_RESULT_NOTEXIST = 1;
?? ? ? ?? LOGON_RESULT_ERROR_PASSWD = 2;
?? ?? ? ? LOGON_RESULT_ALREADY_LOGON = 3;
?? ?? ? ? LOGON_RESULT_SERVER_ERROR = 4;
?? ?? }
?? ?? message UserInfo {
?? ? ? ?? required int64 acctID = 1;
?? ? ?? ? required string name = 2;
?? ?? ? ? required UserStatus status = 3;
?? ?? }
?? ?? message LogonRespMessage {
?? ? ?? ? required LoginResult logonResult = 1;
?? ?? ? ? required UserInfo userInfo = 2; //這里嵌套了UserInfo消息。
?? ?? }
? ? ? 對于上述消息生成的Java代碼,UserInfo因為只是包含了原始類型字段,因此和上例中的LogonReqMessage沒有太多的差別,這里也就不在重復列出了。由于LogonRespMessage消息中嵌套了UserInfo類型的字段,在這里我們將僅僅給出該消息生成的Java代碼和關鍵性注釋。 1 public static final class LogonRespMessage extends2 com.google.protobuf.GeneratedMessageLite3 implements LogonRespMessageOrBuilder {4 5 //Message類的通用性函數定義。6 ... ...7 8 // required .LoginResult logonResult = 1;9 public static final int LOGONRESULT_FIELD_NUMBER = 1; 10 public boolean hasLogonResult() { 11 return ((bitField0_ & 0x00000001) == 0x00000001); 12 } 13 public com.lsk.lyphone.LYPhoneMessage.LoginResult getLogonResult() { 14 return logonResult_; 15 } 16 17 // required .UserInfo userInfo = 2; 18 public static final int USERINFO_FIELD_NUMBER = 2; 19 public boolean hasUserInfo() { 20 return ((bitField0_ & 0x00000002) == 0x00000002); 21 } 22 public com.lsk.lyphone.LYPhoneMessage.UserInfo getUserInfo() { 23 return userInfo_; 24 } 25 //Message類的通用性函數定義。可參照上一小節中的代碼和注釋。 26 ... ... 27 28 public static final class Builder extends 29 com.google.protobuf.GeneratedMessageLite.Builder< 30 com.lsk.lyphone.LYPhoneMessage.LogonRespMessage, Builder> 31 implements com.lsk.lyphone.LYPhoneMessage.LogonRespMessageOrBuilder { 32 33 //一些適用于絕大多數Builder對象的通用性方法。 34 ... ... 35 36 //當前示例中Builder生成的代碼和上一小節中生成的代碼非常類似,這里就不一一贅述了。 37 //和前面的例子相比一個重要的差別是setUserInfo函數多提供了一種函數簽名,其參數為 38 //UserInfo類的Builder對象。這樣調用者在使用時可以直接將Builder對象作為參數傳入。 39 public Builder setUserInfo(com.lsk.lyphone.LYPhoneMessage.UserInfo.Builder builderForValue) { 40 userInfo_ = builderForValue.build(); 41 bitField0_ |= 0x00000002; 42 return this; 43 } 44 } 45 }
????? 下面是讀寫LogonRespMessage對象的Java測試代碼和說明性注釋。
1 private static void testNestedMessage() {2 System.out.println("==================This is nested message.================");3 LogonRespMessage.Builder logonRespBuilder = LogonRespMessage.newBuilder();4 logonRespBuilder.setLogonResult(LoginResult.LOGON_RESULT_SUCCESS);5 UserInfo.Builder userInfo = UserInfo.newBuilder();6 userInfo.setAcctID(200);7 userInfo.setName("Tester");8 userInfo.setStatus(UserStatus.OFFLINE);9 //這里也可以直接傳遞userInfo對象作為參數。因為LogonRespBuilder類提供了setUserInfo的方法重載。 10 logonRespBuilder.setUserInfo(userInfo.build()); 11 LogonRespMessage logonResp = logonRespBuilder.build(); 12 int length = logonResp.getSerializedSize(); 13 System.out.println("The result length is " + length); 14 byte[] buf = logonResp.toByteArray(); 15 16 try { 17 LogonRespMessage logonResp2 = LogonRespMessage.parseFrom(buf); 18 UserInfo userInfo2 = logonResp2.getUserInfo(); 19 System.out.println("LogonResult = " + logonResp2.getLogonResult().toString() + " acctID = " 20 + userInfo2.getAcctID() + " name = " + userInfo2.getName() + " status = " + userInfo2.getStatus().toString()); 21 } catch (InvalidProtocolBufferException e) { 22 e.printStackTrace(); 23 } 24 System.out.println("Reading data from local file generated by C++"); 25 try { 26 LogonRespMessage logonResp3 = LogonRespMessage.parseFrom(new FileInputStream("C:/Mine/LogonResp.dat")); 27 UserInfo userInfo3 = logonResp3.getUserInfo(); 28 System.out.println("LogonResult = " + logonResp3.getLogonResult().toString() + " acctID = " 29 + userInfo3.getAcctID() + " name = " + userInfo3.getName() + " status = " + userInfo3.getStatus().toString()); 30 } catch (FileNotFoundException e) { 31 e.printStackTrace(); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 }repeated嵌套message生成的Java代碼
?? ?? message BuddyInfo {?? ?? ??? required UserInfo userInfo = 1;
?? ?? ? ? required int32 groupID = 2;
?? ?? }
?? ?? message RetrieveBuddiesResp {
?? ?? ? ? required int32 buddiesCnt = 1;
?? ?? ? ? repeated BuddyInfo buddiesInfo = 2;
?? ?? }
? ? ? 對于上述消息生成的代碼,我們將只是針對RetrieveBuddiesResp消息所對應的Java代碼進行詳細說明,其余部分和前面小節的例子基本相同,可直接參照。而對于RetrieveBuddiesResp類中的代碼,我們也僅僅是對buddiesInfo字段生成的代碼進行更為詳細的解釋。 1 public static final class RetrieveBuddiesResp extends2 com.google.protobuf.GeneratedMessageLite3 implements RetrieveBuddiesRespOrBuilder {4 //這里均為Protocol Buffer生成的通用性代碼。5 ... ...6 // repeated .BuddyInfo buddiesInfo = 2;7 public static final int BUDDIESINFO_FIELD_NUMBER = 2;8 //對于repeated類型的字段,均返回類型參數為字段類型的泛型容器對象。9 public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() { 10 return buddiesInfo_; 11 } 12 public java.util.List<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder> getBuddiesInfoOrBuilderList() { 13 return buddiesInfo_; 14 } 15 public int getBuddiesInfoCount() { 16 return buddiesInfo_.size(); 17 } 18 public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) { 19 return buddiesInfo_.get(index); 20 } 21 public com.lsk.lyphone.LYPhoneMessage.BuddyInfoOrBuilder getBuddiesInfoOrBuilder(int index) { 22 return buddiesInfo_.get(index); 23 } 24 25 //這里仍有一些Protocol Buffer生成的通用性代碼。 26 ... ... 27 28 public static final class Builder extends 29 com.google.protobuf.GeneratedMessageLite.Builder< 30 com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesResp, Builder> 31 implements com.lsk.lyphone.LYPhoneMessage.RetrieveBuddiesRespOrBuilder { 32 33 //這里僅列出和操作repeated字段相關的方法,其他的方法和前面的例子基本一致。 34 // repeated .BuddyInfo buddiesInfo = 2; 35 //本來打算給出比較詳細的說明,但是看到Google為每個函數的命名之后就放棄這個想法, 36 //這樣一來不僅可以避免畫蛇添足,而且也節省了時間。:) 37 public java.util.List<com.lsk.lyphone.LYPhoneMessage.BuddyInfo> getBuddiesInfoList() { 38 return java.util.Collections.unmodifiableList(buddiesInfo_); 39 } 40 public int getBuddiesInfoCount() { 41 return buddiesInfo_.size(); 42 } 43 public com.lsk.lyphone.LYPhoneMessage.BuddyInfo getBuddiesInfo(int index) { 44 return buddiesInfo_.get(index); 45 } 46 public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) { 47 ... ... 48 } 49 public Builder setBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) { 50 ... ... 51 } 52 public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) { 53 ... ... 54 } 55 public Builder addBuddiesInfo(int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo value) { 56 ... ... 57 } 58 public Builder addBuddiesInfo(com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) { 59 ... ... 60 } 61 public Builder addBuddiesInfo( 62 int index, com.lsk.lyphone.LYPhoneMessage.BuddyInfo.Builder builderForValue) { 63 ... ... 64 } 65 public Builder addAllBuddiesInfo( 66 java.lang.Iterable<? extends com.lsk.lyphone.LYPhoneMessage.BuddyInfo> values) { 67 ... ... 68 } 69 public Builder clearBuddiesInfo() { 70 ... ... 71 } 72 public Builder removeBuddiesInfo(int index) { 73 ... ... 74 } 75 } 76 }
????? 下面是讀寫RetrieveBuddiesResp對象的Java測試代碼和說明性注釋。
1 private static void testRepeatedMessage() {2 System.out.println("==================This is repeated message.================");3 RetrieveBuddiesResp.Builder retrieveBuddiesBuilder = RetrieveBuddiesResp.newBuilder();4 retrieveBuddiesBuilder.setBuddiesCnt(2);5 BuddyInfo.Builder buddyInfoBuilder = BuddyInfo.newBuilder();6 buddyInfoBuilder.setGroupID(20);7 UserInfo.Builder userInfoBuilder = UserInfo.newBuilder();8 userInfoBuilder.setAcctID(200);9 userInfoBuilder.setName("user1"); 10 userInfoBuilder.setStatus(UserStatus.OFFLINE); 11 buddyInfoBuilder.setUserInfo(userInfoBuilder.build()); 12 retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder.build()); 13 14 buddyInfoBuilder = BuddyInfo.newBuilder(); 15 buddyInfoBuilder.setGroupID(21); 16 userInfoBuilder = UserInfo.newBuilder(); 17 userInfoBuilder.setAcctID(201); 18 userInfoBuilder.setName("user2"); 19 userInfoBuilder.setStatus(UserStatus.ONLINE); 20 buddyInfoBuilder.setUserInfo(userInfoBuilder); 21 retrieveBuddiesBuilder.addBuddiesInfo(buddyInfoBuilder); 22 RetrieveBuddiesResp buddiesResp = retrieveBuddiesBuilder.build(); 23 24 int length = buddiesResp.getSerializedSize(); 25 System.out.println("The result length is " + length); 26 byte[] buf = buddiesResp.toByteArray(); 27 28 try { 29 RetrieveBuddiesResp buddiesResp2 = RetrieveBuddiesResp.parseFrom(buf); 30 System.out.println("BuddiesCount = " + buddiesResp2.getBuddiesCnt()); 31 System.out.println("Repeated Size = " + buddiesResp2.getBuddiesInfoCount()); 32 for (int i = 0; i < buddiesResp2.getBuddiesInfoCount(); ++i) { 33 BuddyInfo buddyInfo = buddiesResp2.getBuddiesInfo(i); 34 UserInfo userInfo = buddyInfo.getUserInfo(); 35 System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID() 36 + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus()); 37 } 38 39 } catch (InvalidProtocolBufferException e) { 40 e.printStackTrace(); 41 } 42 System.out.println("Reading data from local file generated by C++"); 43 try { 44 RetrieveBuddiesResp buddiesResp3 = RetrieveBuddiesResp.parseFrom(new FileInputStream("C:/Mine/RetrieveBuddiesResp.dat")); 45 System.out.println("BuddiesCount = " + buddiesResp3.getBuddiesCnt()); 46 System.out.println("Repeated Size = " + buddiesResp3.getBuddiesInfoCount()); 47 List<BuddyInfo> buddiesInfo = buddiesResp3.getBuddiesInfoList(); 48 for (BuddyInfo buddyInfo : buddiesInfo) { 49 UserInfo userInfo = buddyInfo.getUserInfo(); 50 System.out.println("GroupID = " + buddyInfo.getGroupID() + " UserInfo.acctID = " + userInfo.getAcctID() 51 + " UserInfo.name = " + userInfo.getName() + " UserInfo.status = " + userInfo.getStatus()); 52 } 53 } catch (FileNotFoundException e) { 54 e.printStackTrace(); 55 } catch (IOException e) { 56 e.printStackTrace(); 57 } 58 }? ? ? 對于Java而言,我們可以通過Maven工具生成兩個jar包,其中一個是protobuf-java-2.4.1.jar,主要用于optimize_for選項為非LITE_RUNTIME的情況,而另一個protobuf-java-2.4.1-lite.jar文件則恰恰與之相反。另外,我通過Beyond Compare工具對這兩個jar包進行了二進制比較后發現,他們是完全相同的。這里之所以仍以LITE版本為例,主要還是因為和之前一篇Blog(C++實例)想匹配。
?? ?? 最后需要說明的是,Protocol Buffer仍然提供了很多其它非常有用的功能,特別是針對序列化的目的地,比如文件流和網絡流等。與此同時,也提供了完整的官方文檔和規范的命名規則,在很多情況下,可以直接通過函數的名字便可獲悉函數所完成的工作
總結
以上是生活随笔為你收集整理的Protocol Buffer Java应用实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Protocol Buffer C++应
- 下一篇: Protocol Buffer数据编码