proto對(duì)象與Java對(duì)象轉(zhuǎn)換工具類(1)— 簡(jiǎn)單對(duì)象轉(zhuǎn)化

proto對(duì)象與Java對(duì)象轉(zhuǎn)換工具類(1)— 簡(jiǎn)單對(duì)象轉(zhuǎn)化

proto對(duì)象與Java對(duì)象轉(zhuǎn)換工具類(2)— 處理Map<String,Object>類型

背景:在使用grpc通信過(guò)程中,需要定義proto協(xié)議龄捡。proto協(xié)議會(huì)生成pb對(duì)象须眷,為了不讓pb對(duì)象侵入到業(yè)務(wù)代碼,所以需要將pb對(duì)象轉(zhuǎn)化成java對(duì)象庄撮。如何降低get凹嘲、set的工作量堂飞?實(shí)現(xiàn)快速?gòu)?fù)制(深拷貝)?

1爽篷、pb與java對(duì)象轉(zhuǎn)化

1.1 不能利用反射

首先我們不能簡(jiǎn)單的使用反射機(jī)制(無(wú)論是操作field字段悴晰,還是操作getter/setter方法)。因?yàn)閜b方法生成的對(duì)象有以下特點(diǎn):

  • ProtoBean不允許有null值逐工,而Pojo允許有null值铡溪,從Pojo拷貝到Proto必然會(huì)有非空異常
  • BeanUtils 會(huì)按照方法名及getter/setter類型進(jìn)行匹配,嵌套類型因?yàn)轭愋筒黄ヅ涠鵁o(wú)法正忱岷埃拷貝
  • Map和List的Proto屬性生成的Java會(huì)分別在屬性名后增加Map和List棕硫,如果希望能夠進(jìn)行拷貝,則需要按照這個(gè)規(guī)則明明Projo的屬性名
  • Enum類型不匹配無(wú)法進(jìn)行拷貝袒啼,如果希望能夠進(jìn)行拷貝哈扮,可以嘗試使用ProtoBean的Enum域的get**Value()方法,并據(jù)此命名Pojo屬性名

1.2 通過(guò)序列化方式

引入依賴:

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java-util -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java-util</artifactId>
    <version>3.7.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.googlecode.protobuf-java-format/protobuf-java-format -->
<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>

工具方法:

import java.io.IOException;

import com.google.gson.Gson;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;

/**
 * 相互轉(zhuǎn)化的兩個(gè)對(duì)象的getter和setter字段要完全的匹配蚓再。
 * 此外滑肉,對(duì)于ProtoBean中的enum和bytes,與POJO轉(zhuǎn)化時(shí)遵循如下的規(guī)則:
 * <ol>
 *     <li>enum -> String</li>
 *     <li>bytes -> base64 String</li>
 * </ol>
 * @author Yang Guanrong
 * @date 2019/08/18 23:44
 */
public class ProtoBeanUtils {

    /**
     * 將ProtoBean對(duì)象轉(zhuǎn)化為POJO對(duì)象
     *
     * @param destPojoClass 目標(biāo)POJO對(duì)象的類類型
     * @param sourceMessage 含有數(shù)據(jù)的ProtoBean對(duì)象實(shí)例
     * @param <PojoType> 目標(biāo)POJO對(duì)象的類類型范型
     * @return
     * @throws IOException
     */
    public static <PojoType> PojoType toPojoBean(Class<PojoType> destPojoClass, Message sourceMessage)
        throws IOException {
        if (destPojoClass == null) {
            throw new IllegalArgumentException
                ("No destination pojo class specified");
        }
        if (sourceMessage == null) {
            throw new IllegalArgumentException("No source message specified");
        }
        String json = JsonFormat.printer().print(sourceMessage);
        return new Gson().fromJson(json, destPojoClass);
    }

    /**
     * 將POJO對(duì)象轉(zhuǎn)化為ProtoBean對(duì)象
     *
     * @param destBuilder 目標(biāo)Message對(duì)象的Builder類
     * @param sourcePojoBean 含有數(shù)據(jù)的POJO對(duì)象
     * @return
     * @throws IOException
     */
    public static void toProtoBean(Message.Builder destBuilder, Object sourcePojoBean) throws IOException {
        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);
    }
}

注意:如果如果sourcePojoBean存在摘仅,但是destBuilder不存在靶庙,則會(huì)拋出異常。如果不想拋出異常娃属,需要使用如下api
JsonFormat.parser().ignoringUnknownFields().merge(json, destBuilder);

2六荒、泛型對(duì)象的轉(zhuǎn)換

背景:我們?cè)诙xproto到j(luò)ava對(duì)象的轉(zhuǎn)換時(shí),需要在BO的對(duì)象上在定義一層Resp對(duì)象矾端。如何將這一層的Resp對(duì)象泛型化掏击?降低創(chuàng)建對(duì)象的成本?

集合對(duì)象:

@Data
public class CollectionResp<T> {

    private Collection<T> infos;

    public static <T> CollectionResp<T> build() {
        return new CollectionResp<>();
    }

    public static <T> CollectionResp<T> build(Collection<T> info) {
        CollectionResp<T> resp = new CollectionResp<>();
        resp.setInfos(info);
        return resp;
    }
}

單個(gè)對(duì)象:

@Data
public class SingleResp<T> {

    private T info;

    public static <T> SingleResp<T> build() {
        return new SingleResp<>();
    }

    public static <T> SingleResp<T> build(T info) {
        SingleResp<T> resp = new SingleResp<>();
        resp.setInfo(info);
        return resp;
    }
}

分頁(yè)對(duì)象:

@Data
public class PageResp<T> {

    /**
     * 指標(biāo)明細(xì)
     */
    private List<T> details;

    /**
     * 分頁(yè)參數(shù)
     */
    @ApiModelProperty("分頁(yè)參數(shù)")
    private ClueCrmPageSplitInfo pagingInfo;

    public static <T> PageResp<T> defaultObj() {
        return new PageResp<T>();
    }

pb格式:

message xxxResp{
   BaseResultPbInfo  result;
   xxxBo  info;
}

統(tǒng)一處理:

return toSingleResp(new TypeReference<SingleResp<XxxResp>>() {}, service.xxx(入?yún)?;

工具類:

    //反射拿到通用對(duì)象
    private static BaseResultPbInfo getBaseResultPbInfo(com.google.protobuf.Message sourceMessage) {
        Field baseResultPbInfoField =
                ReflectionUtils.findField(sourceMessage.getClass(), "result_", BaseResultPbInfo.class);
        BaseResultPbInfo baseResultPbInfo = null;
        if (baseResultPbInfoField != null) {
            baseResultPbInfoField.setAccessible(true);
            baseResultPbInfo = (BaseResultPbInfo) ReflectionUtils.getField(baseResultPbInfoField, sourceMessage);
        }
        return baseResultPbInfo;
    }

  //拼接響應(yīng)對(duì)象
    public static <PojoType> Message<SingleResp<PojoType>> toSingleResp(TypeReference<SingleResp<PojoType>> reference,
                                                                        com.google.protobuf.Message sourceMessage) {
        BaseResultPbInfo baseResultPbInfo = getBaseResultPbInfo(sourceMessage);
        if (baseResultPbInfo == null || Objects.equals(TctResultCode.SUCCESS.getCode(), baseResultPbInfo.getCode())) {
            return Message.ok(PbUtils.toPojoSingleRespBean(reference, sourceMessage));
        } else {
            return Message.error(baseResultPbInfo.getCode(), baseResultPbInfo.getErrorMsg());
        }
    }

    //序列化獲取到業(yè)務(wù)對(duì)象
    public static <PojoType> SingleResp<PojoType> toPojoSingleRespBean(TypeReference<SingleResp<PojoType>> reference, Message sourceMessage) {
        try {
            String json = ObjectMapperUtils.toJSON(sourceMessage);
            return ObjectMapperUtils.mapper().readValue(json, reference);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

3秩铆、java對(duì)象快速生成proto協(xié)議

idea下載pojo to proto插件砚亭。插件地址

類文件-右鍵-PojoProto, 將簡(jiǎn)單Java類型轉(zhuǎn)成proto message拷貝至剪貼板

詳見--文章

4、 proto協(xié)議如何傳遞null?

引入代碼
import "google/protobuf/wrappers.proto";

Wrappers 的目標(biāo)是解決基本類型存在不為空的默認(rèn)值的問(wèn)題钠惩,比如 int32 的默認(rèn)值是 0,當(dāng)我解析出一個(gè) 0 的時(shí)候族阅,無(wú)法確定是真的 0 還是這個(gè)字段不存在篓跛。要解決也很簡(jiǎn)單,因?yàn)閺?fù)合類型中的 message 是存在「空」的坦刀,只要把空判斷交給 message 類型就可以了愧沟。Wrappers 提供了所有基本類型的「包裝」message。

如何使用:

import "google/protobuf/wrappers.proto";

message InfoResp {
  google.protobuf.UInt64Value biz_id = 1;
}

如何解析:

Long bizId=request.hasBizId()?request.getBizId().getValue():null;

詳見【菜狗教程】Protobuf.02 - 定義 message

文章參考

Protobuf與POJO的相互轉(zhuǎn)化 - 通過(guò)Json

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲤遥,一起剝皮案震驚了整個(gè)濱河市沐寺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盖奈,老刑警劉巖混坞,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異钢坦,居然都是意外死亡究孕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門爹凹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厨诸,“玉大人,你說(shuō)我怎么就攤上這事禾酱∥⒊辏” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵颤陶,是天一觀的道長(zhǎng)颗管。 經(jīng)常有香客問(wèn)我,道長(zhǎng)指郁,這世上最難降的妖魔是什么忙上? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮闲坎,結(jié)果婚禮上疫粥,老公的妹妹穿的比我還像新娘。我一直安慰自己腰懂,他們只是感情好梗逮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绣溜,像睡著了一般慷彤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天底哗,我揣著相機(jī)與錄音岁诉,去河邊找鬼。 笑死跋选,一個(gè)胖子當(dāng)著我的面吹牛涕癣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播前标,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼坠韩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了炼列?” 一聲冷哼從身側(cè)響起只搁,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俭尖,沒(méi)想到半個(gè)月后氢惋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡目溉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年明肮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缭付。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柿估,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出陷猫,到底是詐尸還是另有隱情秫舌,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布绣檬,位于F島的核電站足陨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏娇未。R本人自食惡果不足惜墨缘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望零抬。 院中可真熱鬧镊讼,春花似錦、人聲如沸平夜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忽妒。三九已至玩裙,卻和暖如春兼贸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吃溅。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工溶诞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人决侈。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓很澄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親颜及。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容