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