上篇文章我们遗留了几个问题
1、protobuf 有没有数据类型?protobuf 怎么与 Java 数据类型对应?
2、protobuf 怎么运用到我们的项目当中?复杂的 List、Map、内嵌对象等等怎么实现?
3、protobuf 怎么和 JSON 互相转换?
4、protobuf 与 Java 对象互转
别急,我们现在继续深入,学习就是要不断深入了解,只有更深入,你才能体会到快乐和成就感
继续接着上一个项目来写
required : 字段只能也必须出现 1 次,多用于必填项,必须赋值的字符
例如:
required int32 id = 1 [default = 123]
optional : 字段可出现 0 次或多次,可有可无的字段,可以使用[default = xxx]配置默认值
例如:
optional string name = 1 [default = "张三"]
repeated : 字段可出现任意多次(包括 0),多用于 Java List 属性
例如:
//list String
repeated string strList = 5;
//list 对象
repeated Role roleList = 6;
1 ~ 536870911(除去 19000 到 19999 之间的标识号, Protobuf 协议实现中对这些进行了预留。如果非要在.proto 文件中使用这些预留标识号,编译时就会报警)
在消息定义中,每个字段都有唯一的一个标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改 变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该尽可能为那些频繁出现的消息元素保留 [1,15]之内的标识号
protobuf 类型 | java 类型 |
---|---|
double | double |
float | float |
int32 | int |
int64 | long |
bool | boolean |
string | String |
系统默认值:
string默认为空字符串
bool默认为false
数值默认为0
enum默认为第一个元素
//创建一个 User 对象
message User{
//list Int
repeated int32 intList = 1;
//list String
repeated string strList = 5;
}
//创建一个 User 对象
message User{
//list 对象
repeated Role roleList = 6;
}
//创建一个 User 对象
message User{
// 定义简单的 Map string
map<string, int32> intMap = 7;
// 定义复杂的 Map 对象
map<string, string> stringMap = 8;
}
//创建一个 User 对象
message User{
// 定义复杂的 Map 对象
map<string, MapVauleObject> mapObject = 8;
}
// 定义 Map 的 value 对象
message MapVauleObject {
string code = 1;
string name = 2;
}
//创建一个 User 对象
message User{
// 对象
NickName nickName = 4;
}
// 定义一个新的Name对象
message NickName {
string nickName = 1;
}
看完上面的,你应该明白要怎么定义一个 proto 对象的属性
格式如下
字段规则(可选) 字段类型 字段名称 字段标识符 字段默认值(可选)
例如:
Java 实体类写法
private String name;
proto 写法
string name = 1;
Java 实体类写法
private List list;
proto 写法
repeated string list = 1;
看这个之前,应该耐心把上面的数据类型对应先了解清楚
这个文件包含了我们平常 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;
}
同样的,代码拿过去之后,有报错不要认为自己有问题,语法高亮罢了
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;
String jsonObject = null;
try {
//反序列化
user1 = UserProto.User.parseFrom(s);
System.out.println("反序列化:\n" + user1.toString());
System.out.println("中文反序列化:\n" + printToUnicodeString(user1));
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
// System.out.println("***********************************************");
// //中文反序列化时会转成八进制,可采用 TextFormat.printToUnicodeString 进行转换
// System.out.println("直接反序列化:\n" + printToUnicodeString(user1));
}
/**
* 处理反序列化时中文出现的八进制问题(属性值为中文时可能会出现这样的八进制\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);
}
}
可以看到我们中文会乱码,不过问题不大,项目当中我们肯定也不是这样去使用
使用这个转换必须要使用 protobuf 的 java util jar 包
<!-- proto 与 Json 互转会用到-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.15.3</version>
</dependency>
String json = JsonFormat.printer().print(sourceMessage);
//ignoringUnknownFields 如果 json 串中存在的属性 proto 对象中不存在,则进行忽略,否则会抛出异常
JsonFormat.parser().ignoringUnknownFields().merge(json, targetBuilder);
return targetBuilder.build();
package com.wxw.notes.protobuf.util;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
/**
* 注意:
* - 该实现无法处理含有Any类型字段的Message
* - enum类型数据会转化为enum的字符串名
* - bytes会转化为utf8编码的字符串
*
以上这段暂未进行测试
*
* @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 对象 bulider
* @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;
}
}
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);
}
}
本处使用了 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>
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;
}
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;
}
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;
}
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;
}
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.JsonUtil;
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());
}
至此,我们遗留的四个问题已经全部解决,学会基础入门篇和深入复杂篇之后,我们能够基本满足了日常开发
项目源码地址:https://github.com/wxwhowever/springboot-notes/tree/main/protobuf