1. 程式人生 > 程式設計 >如何對protobuf生成的Java物件進行引數校驗

如何對protobuf生成的Java物件進行引數校驗

前言


時隔幾月在部落格方面,筆者並沒有想寫文的衝動,不過最近筆者負責一個新服務的開發,在過程中使用了protobuf相關技術。開發完成之後,由於在引數校驗這塊使用了大量的if-else進行校驗,造成code review過程中被瘋狂的diss。在此,咱們就話不多說,直接引出問題:如何對protobuf生成的java物件進行引數校驗?

名詞解釋:

  • protobuf生成的java物件統稱為pb物件。普通的java物件統稱為pojo物件
  • hibernate validator統稱為validator

正文


比如protobuf直接生成的物件request,一開始筆者是這樣進行校驗的:

if(!request.hasXXX()) {
  // do....
throw new Exception(); } else if(request.hasXXX) { ..... throw new Exception(); } ....以下省略多個if-else 複製程式碼

為什麼會使用這種複雜校驗方式?一開始筆者也曾想過使用hibernate validator進行自動校驗,但在實現過程中,發現pb物件體積較大,內部屬性繁複無法對其加上validator校驗註解。後來,筆者便想方設法讓pb物件轉成普通的java pojo物件,一旦實現無縫轉換,那麼就可以使用hibernate validator進行自動校驗了。

所以,首先想到的就是spring或者apache中的beanutils工具,利用該工具可以實現相同屬性名稱的值轉換,不過這裡有一個缺陷:當基礎資料型別的屬性的值為null的時候,beanutils會進行賦值操作。

比如:Integer i = null,複製出來的屬性會變成,Integer i = 0。此時,如果用轉換後的pojo物件進行@NotNull校驗,校驗結果會直接通過,並不會丟擲異常資訊。在開發過程中,很多時候僅僅是需要null,而不是具有預設值的屬性,那麼此時spring或apache中的beanutils工具就不再適合了。

當然,apache的beanutils工具通過註冊代理的方式可以解決null值變預設值問題。但最終筆者並沒有採取beanutils工具,原因是apache的beanutils的copyProperties方法效率較低,並不推薦使用。beanutils註冊方案

工具介紹:複製即可使用
    /**
     * 將ProtoBean物件轉化為POJO物件
     *
     * @param targetClass 目標POJO物件的型別別
     * @param sourceMessage 含有資料的ProtoBean物件例項
     * @param <PojoType> 目標POJO物件的類型別範型
     * @return
     * @throws IOException
     */
    public static <PojoType> PojoType toBean(Message sourceMessage,Class<PojoType> targetClass) throws Exception {
        if (targetClass == null) {
            throw new IllegalArgumentException("No destination pojo class specified");
        }
        if (sourceMessage == null) {
            throw new IllegalArgumentException("protobuf message is no specified");
        }
        String json = JsonFormat.printer().print(sourceMessage);
        return new Gson().fromJson(json,targetClass);
    }


    /**
     * 將POJO物件轉化為ProtoBean物件
     *
     * @param destBuilder 目標Message物件的Builder類
     * @param sourcePojoBean 含有資料的POJO物件
     * @return
     * @throws IOException
     */
    public static void toProto(Message.Builder destBuilder,Object sourcePojoBean) throws Exception {
        if (destBuilder == null) {
            throw new IllegalArgumentException
                    ("No destination message builder specified");
        }
        if (sourcePojoBean == null) {
            throw new IllegalArgumentException("No source pojo specified");
        }
        String json = new Gson().toJson(sourcePojoBean);
        JsonFormat.parser().merge(json,destBuilder);
    }
複製程式碼

在使用該工具的時候,需要新增依賴:

<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java-util</artifactId>
	// 版本省略
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
</dependency>
複製程式碼

由於該工具類並不是筆者所寫,所以直接貼上原作連結

注意事項:pojo的物件的基本資料型別必須是包裝類

最後,筆者還想說說關於pojo轉pb物件問題,使用上面的工具中的toProto方法進行轉換的時候,屬性名稱和屬性數量要一致,不然會丟擲異常。

結束語

經過前後好幾天的探索,發現直接對pb物件直接進行校驗是行不通的,一般會利用中介進行轉換,這裡就使用了pb->json->pojo的策略。筆者認為工具中的toBean方法最為重要,利用該方法可以實現必填項引數校驗,這樣極大減少if-else語句的數量。當然,toProto方法可以極大省略pb.setXX()方法的數量,也是挺不錯的。 說實話,筆者真羨慕那些能自己造輪子的人...想起自己實現的工具,心裡就一陣火大。