上篇文章我们遗留了几个问题:
- Protobuf 有哪些数据类型?Protobuf 如何与 Java 数据类型对应?
- Protobuf 如何运用到我们的项目当中?复杂的 List、Map、内嵌对象等如何实现?
- Protobuf 如何和 JSON 互相转换?
- Protobuf 与 Java 对象如何互转?
别急,我们现在继续深入,学习就是要不断深入了解,只有更深入,你才能体会到快乐和成就感。
继续接着上一个项目来写。
一、Protobuf 与 Java 数据类型对应
1. 字段规则
required: 字段只能也必须出现 1 次,多用于必填项,必须赋值的字段。
例如:
required int32 id = 1 [default = 123];
optional: 字段可出现 0 次或 1 次,可有可无的字段,可以使用
[default = xxx]
配置默认值。例如:
optional string name = 1 [default = "张三"];
repeated: 字段可出现任意多次(包括 0),多用于 Java 的 List 属性。
例如:
// list String repeated string strList = 5; // list 对象 repeated Role roleList = 6;
2. 字段编号(标识符)
字段编号范围是 1 ~ 536870911(除去 19000 到 19999 之间的标识号,Protobuf 协议实现中对这些进行了预留。如果在 .proto 文件中使用这些预留标识号,编译时会报警)。
在消息定义中,每个字段都有唯一的一个标识符。这些标识符用于在消息的二进制格式中识别各个字段,一旦开始使用就不能改变。注:[1,15] 之内的标识号在编码时会占用一个字节,[16,2047] 之内的标识号则占用两个字节。所以应该尽可能为那些频繁出现的消息元素保留 [1,15] 之内的标识号。
3. 字段类型
3.1 基本常用类型
Protobuf 类型 | Java 类型 |
---|---|
double | double |
float | float |
int32 | int |
int64 | long |
bool | boolean |
string | String |
系统默认值:
- string 默认为空字符串
- bool 默认为 false
- 数值默认为 0
- enum 默认为第一个元素
3.2 复杂类型
Java String、Integer List 在 Protobuf 的定义
// 创建一个 User 对象 message User { // list Int repeated int32 intList = 1; // list String repeated string strList = 5; }
Java 对象 List 在 Protobuf 的定义
// 创建一个 User 对象 message User { // list 对象 repeated Role roleList = 6; }
Java String、Integer Map 在 Protobuf 的定义
// 创建一个 User 对象 message User { // 定义简单的 Map string map<string, int32> intMap = 7; // 定义复杂的 Map 对象 map<string, string> stringMap = 8; }
Java 对象 Map 在 Protobuf 的定义
// 创建一个 User 对象 message User { // 定义复杂的 Map 对象 map<string, MapVauleObject> mapObject = 8; } // 定义 Map 的 value 对象 message MapVauleObject { string code = 1; string name = 2; }
Java 实体类中嵌套实体 在 Protobuf 的定义
// 创建一个 User 对象 message User { // 对象 NickName nickName = 4; } // 定义一个新的 Name 对象 message NickName { string nickName = 1; }
4. 定义 Protobuf 的一个属性
看完上面的,你应该明白要怎么定义一个 Protobuf 对象的属性。
格式如下:字段规则(可选) 字段类型 字段名称 字段标识符 字段默认值(可选)
例如:
一个相当于 Java 中的 String 类型
Java 实体类写法:
private String name;
Protobuf 写法:
string name = 1;
一个相当于 Java 中的 list 类型
Java 实体类写法:
private List<String> list;
Protobuf 写法:
repeated string list = 1;
二、实现 Java 中复杂的对象嵌套
在看这个之前,应该先耐心了解上面的数据类型对应。
1. 直接上一个成品 .proto 文件
这个文件包含了我们平常 Java 实体类属性的基本用法,比如 int、String、内置对象、内置 List、内置 Map。
// 使用 proto3 语法, 未指定则使用 proto2
syntax = "proto3";
// proto 文件包名
package com.wxw.notes.protobuf.proto;
// 生成 proto 文件所在包路径,一般来说是和文件包名一致就可以
option java_package = "com.wxw.notes.protobuf.proto";
// 生成 proto 的文件名
option java_outer_classname = "UserProto";
// 引入外部的 proto 对象
import "Role.proto";
// 创建一个 User 对象
message User {
// 自身属性
int32 id = 1;
string code = 2;
string name = 3;
// 对象
NickName nickName = 4;
// list 引用类型
repeated string strList = 5;
// list 对象(此对象为引入的外部 proto 文件)
repeated Role roleList = 6;
// 定义简单的 Map string
map<string, string> map = 7;
// 定义复杂的 Map 对象
map<string, MapVauleObject> mapObject = 8;
}
// 定义一个新的 Name 对象
message NickName {
string nickName = 1;
}
// 定义 Map 的 value 对象
message MapVauleObject {
string code = 1;
string name = 2;
}
同样的,代码拿过去之后,如果有报错不要认为自己有问题,语法高亮罢了。
2. 生成 Protobuf 对象
选中 User.proto
文件,右键生成对应对象。
3. 编写测试类
package com.wxw.notes.protobuf.test;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.TextFormat;
import com.wxw.notes.protobuf.proto.RoleProto;
import com.wxw.notes.protobuf.proto.UserProto;
import java.util.Arrays;
public class ComplexTestMain {
public static void main(String[] args) {
// 初始化数据
UserProto.User.Builder user = UserProto.User.newBuilder();
user.setId(1)
.setCode("001")
.setName("张三")
.build();
// 内部对象
UserProto.NickName.Builder nickName = UserProto.NickName.newBuilder();
user.setNickName(nickName.setNickName("昵称").build());
// 简单 list
user.addStrList("01");
user.addStrList("02");
// object list
RoleProto.Role.Builder role1 = RoleProto.Role.newBuilder();
role1.setCode("001");
role1.setName("管理员");
RoleProto.Role.Builder role2 = RoleProto.Role.newBuilder();
role2.setCode("002");
role2.setName("操作员");
user.addRoleList(role1);
user.addRoleList(role2);
// 简单 map
user.putMap("key1", "value1");
user.putMap("key2", "value2");
// object map
UserProto.MapVauleObject.Builder objectMap1 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap1", objectMap1.setCode("code1").setName("name1").build());
UserProto.MapVauleObject.Builder objectMap2 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap2", objectMap2.setCode("code2").setName("name2").build());
// 序列化
UserProto.User build = user.build();
// 转换成字节数组
byte[] s = build.toByteArray();
System.out.println("protobuf数据bytes[]:" + Arrays.toString(s));
System.out.println("protobuf序列化大小: " + s.length);
UserProto.User user1 = null
;
try {
// 反序列化
user1 = UserProto.User.parseFrom(s);
System.out.println("反序列化:\n" + user1.toString());
System.out.println("中文反序列化:\n" + printToUnicodeString(user1));
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 处理反序列化时中文出现的八进制问题(属性值为中文时可能会出现这样的八进制 \346\223\215\344\275\234\345\221\230)
* 可直接使用 protobuf 自带的 TextFormat.printToUnicodeString(message) 方法,但是这个方法过时了,直接从这个方法内部拿出来使用就可以了
*
* @param message 转换的 protobuf 对象
* @return string
*/
public static String printToUnicodeString(MessageOrBuilder message) {
return TextFormat.printer().escapingNonAscii(false).printToString(message);
}
}
4. 测试截图
可以看到我们中文会乱码,不过问题不大,项目当中我们肯定也不是这样去使用。
三、Protobuf 和 JSON 互相转换
使用这个转换必须要使用 Protobuf 的 Java util jar 包。
<!-- proto 与 Json 互转会用到 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.15.3</version>
</dependency>
1. Protobuf 转 Json
String json = JsonFormat.printer().print(sourceMessage);
2. Json 转 Protobuf
// ignoringUnknownFields 如果 json 串中存在的属性 proto 对象中不存在,则进行忽略,否则会抛出异常
JsonFormat.parser().ignoringUnknownFields().merge(json, targetBuilder);
return targetBuilder.build();
3. Protobuf 和 JSON 互转工具类
package com.wxw.notes.protobuf.util;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
/**
* <ul> 注意:
* <li>该实现无法处理含有 Any 类型字段的 Message</li>
* <li>enum 类型数据会转化为 enum 的字符串名</li>
* <li>bytes 会转化为 utf8 编码的字符串</li>
* </ul>
* 以上这段暂未进行测试
* @author wuxiongwei
* @date 2021/5/13 16:04
* @Description proto 与 Json 转换工具类
*/
public class ProtoJsonUtil {
/**
* proto 对象转 JSON
* 使用方法:
* // 反序列化之后
* UserProto.User user1 = UserProto.User.parseFrom(user);
* // 转 json
* String jsonObject = ProtoJsonUtil.toJson(user1);
*
* @param sourceMessage proto 对象
* @return 返回 JSON 数据
* @throws InvalidProtocolBufferException
*/
public static String toJson(Message sourceMessage) throws InvalidProtocolBufferException {
if (sourceMessage != null) {
String json = JsonFormat.printer().includingDefaultValueFields().print(sourceMessage);
return json;
}
return null;
}
/**
* JSON 转 proto 对象
* 使用方法:Message message = ProtoJsonUtil.toObject(UserProto.User.newBuilder(), jsonObject);
*
* @param targetBuilder proto 对象 builder
* @param json json 数据
* @return 返回转换后的 proto 对象
* @throws InvalidProtocolBufferException
*/
public static Message toObject(Message.Builder targetBuilder, String json) throws InvalidProtocolBufferException {
if (json != null) {
// ignoringUnknownFields 如果 json 串中存在的属性 proto 对象中不存在,则进行忽略,否则会抛出异常
JsonFormat.parser().ignoringUnknownFields().merge(json, targetBuilder);
return targetBuilder.build();
}
return null;
}
}
4. 改造测试类
package com.wxw.notes.protobuf.test;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.TextFormat;
import com.wxw.notes.protobuf.proto.RoleProto;
import com.wxw.notes.protobuf.proto.UserProto;
import com.wxw.notes.protobuf.util.ProtoJsonUtil;
import java.io.IOException;
import java.util.Arrays;
public class JsonTestMain {
public static void main(String[] args) {
// 初始化数据
UserProto.User.Builder user = UserProto.User.newBuilder();
user.setId(1)
.setCode("001")
.setName("张三")
.build();
// 内部对象
UserProto.NickName.Builder nickName = UserProto.NickName.newBuilder();
user.setNickName(nickName.setNickName("昵称").build());
// 简单 list
user.addStrList("01");
user.addStrList("02");
// object list
RoleProto.Role.Builder role1 = RoleProto.Role.newBuilder();
role1.setCode("001");
role1.setName("管理员");
RoleProto.Role.Builder role2 = RoleProto.Role.newBuilder();
role2.setCode("002");
role2.setName("操作员");
user.addRoleList(role1);
user.addRoleList(role2);
// 简单 map
user.putMap("key1", "value1");
user.putMap("key2", "value2");
// object map
UserProto.MapVauleObject.Builder objectMap1 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap1", objectMap1.setCode("code1").setName("name1").build());
UserProto.MapVauleObject.Builder objectMap2 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap2", objectMap2.setCode("code2").setName("name2").build();
// 序列化
UserProto.User build = user.build();
// 转换成字节数组
byte[] s = build.toByteArray();
System.out.println("protobuf数据bytes[]:" + Arrays.toString(s));
System.out.println("protobuf序列化大小: " + s.length);
UserProto.User user1 = null;
String jsonObject = null;
try {
// 反序列化
user1 = UserProto.User.parseFrom(s);
// proto 转 json
jsonObject = ProtoJsonUtil.toJson(user1);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println("Json 格式化结果:\n" + jsonObject);
System.out.println("Json 格式化数据大小: " + jsonObject.getBytes().length);
// 将 Json 数据转 proto 对象
try {
Message message = ProtoJsonUtil.toObject(UserProto.User.newBuilder(), jsonObject);
System.out.println("json 转 protobuf 对象:\n " + printToUnicodeString(message));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理反序列化时中文出现的八进制问题(属性值为中文时可能会出现这样的八进制 \346\223\215\344\275\234\345\221\230)
* 可直接使用 protobuf 自带的 TextFormat.printToUnicodeString(message) 方法,但是这个方法过时了,直接从这个方法内部拿出来使用就可以了
*
* @param message 转换的 protobuf 对象
* @return string
*/
public static String printToUnicodeString(MessageOrBuilder message) {
return TextFormat.printer().escapingNonAscii(false).printToString(message);
}
}
5. 测试截图
四、Protobuf 与 Java 对象互转
本处使用了 Lombok 和 Gson,记得下载 Lombok 插件和导入依赖。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
1. 创建对应 Java 实体类
1.1 User
package com.wxw.notes.protobuf.entity;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @author wuxiongwei
* @date 2021/5/13 14:55
* @Description
*/
@Data
public class User {
private int id;
private String code;
private String name;
private NickName nickName;
private List<String> strList;
private List<Role> roleList;
private Map<String,String> map;
private Map<String,MapVauleObject> mapObject;
}
1.2 Role
package com.wxw.notes.protobuf.entity;
import lombok.Data;
/**
* @author wuxiongwei
* @date 2021/5/13 14:55
* @Description
*/
@Data
public class Role {
private String code;
private String name;
}
1.3 NickName
package com.wxw.notes.protobuf.entity;
import lombok.Data;
/**
* @author wuxiongwei
* @date 2021/5/13 14:58
* @Description
*/
@Data
public class NickName {
private String NickName;
}
1.4 MapVauleObject
package com.wxw.notes.protobuf.entity;
import lombok.Data;
/**
* @author wuxiongwei
* @date 2021/5/13 14:59
* @Description
*/
@Data
public class MapVauleObject {
private String code;
private String name;
}
2. 改造测试类
package com.wxw.notes.protobuf.test;
import com.google.protobuf.InvalidProtocolBufferException;
import com.wxw.notes.protobuf.entity.User;
import com.wxw.notes.protobuf.proto.RoleProto;
import com.wxw.notes.protobuf.proto.UserProto;
import com.wxw.notes.protobuf.util.ProtoJsonUtil;
import org.springframework.beans.BeanUtils;
import java.util.Arrays;
public class JavaTestMain {
public static void main(String[] args) {
// 初始化数据
UserProto.User.Builder user = UserProto.User.newBuilder();
user.setId(1)
.setCode("001")
.setName("张三")
.build();
// 内部对象
UserProto.NickName.Builder nickName = UserProto.NickName.newBuilder();
user.setNickName(nickName.setNickName("昵称").build());
// 简单 list
user.addStrList("01");
user.addStrList("02");
// object list
RoleProto.Role.Builder role1 = RoleProto.Role.newBuilder();
role1.setCode("001");
role1.setName("管理员");
RoleProto.Role.Builder role2 = RoleProto.Role.newBuilder();
role2.setCode("002");
role2.setName("操作员");
user.addRoleList(role1);
user.addRoleList(role2);
// 简单 map
user.putMap("key1", "value1");
user.putMap("key2", "value2");
// object map
UserProto.MapVauleObject.Builder objectMap1 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap1", objectMap1.setCode("code1").setName("name1").build());
UserProto.MapVauleObject.Builder objectMap2 = UserProto.MapVauleObject.newBuilder();
user.putMapObject("objectMap2", objectMap2.setCode("code2").setName("name2").build();
// 序列化
UserProto.User build = user.build();
// 转换成字节数组
byte[] s = build.toByteArray();
System.out.println("protobuf数据bytes[]:" + Arrays.toString(s));
System.out.println("protobuf序列化大小: " + s.length);
UserProto.User user1 = null;
String jsonObject = null;
try {
// 反序列化
user1 = UserProto.User.parseFrom(s);
// proto 转 json
jsonObject = ProtoJsonUtil.toJson(user1);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println("Json 格式化结果:\n" + jsonObject);
System.out.println("Json 格式化数据大小: " + jsonObject.getBytes().length);
// 复制 proto 对象到 Java 对象测试,测试下来只能复制基础的属性,内部对象等不可以复制,也就是只有浅拷贝
User user2 = new User();
BeanUtils.copyProperties(user1, user2);
System.out.println("复制后对象:\n " + user2.toString());
// 通过 proto Json 数据转 Java 对象
Gson GSON = new GsonBuilder().serializeNulls().create();
User user3 = GSON.fromJson(jsonObject, User.class);
System.out.println("json 转换之后对象:\n " + user3.toString());
}
}
3. 测试截图
五、小总结
至此,我们遗留的四个问题已经全部解决,学会基础入门篇和深入复杂篇之后,我们能够基本满足日常开发的需求。
项目源码地址:https://github.com/wxwhowever/springboot-notes/tree/main/protobuf
版权声明:本文为 CSDN 博主「WXWhowever」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wxw1997a/article/details/116758401