上篇文章我们遗留了几个问题

1、protobuf 有没有数据类型?protobuf 怎么与 Java 数据类型对应?
2、protobuf 怎么运用到我们的项目当中?复杂的 List、Map、内嵌对象等等怎么实现?
3、protobuf 怎么和 JSON 互相转换?
4、protobuf 与 Java 对象互转
别急,我们现在继续深入,学习就是要不断深入了解,只有更深入,你才能体会到快乐和成就感

继续接着上一个项目来写

一、protobuf 与 Java 数据类型对应
1、字段规则
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;

2、字段编号(标识符):
1 ~ 536870911(除去 19000 到 19999 之间的标识号, Protobuf 协议实现中对这些进行了预留。如果非要在.proto 文件中使用这些预留标识号,编译时就会报警)

在消息定义中,每个字段都有唯一的一个标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改 变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该尽可能为那些频繁出现的消息元素保留 [1,15]之内的标识号

3、字段类型
3.1、基本常用类型

protobuf 类型java 类型
doubledouble
floatfloat
int32int
int64long
boolboolean
stringString

系统默认值:
     string默认为空字符串
     bool默认为false
     数值默认为0
     enum默认为第一个元素

3.2、复杂类型
3.2.1、Java String、Integer List 在 protobuf 的定义
//创建一个 User 对象
message User{
//list Int
repeated int32 intList = 1;
//list String
repeated string strList = 5;
}

3.2.2、Java 对象 List 在 protobuf 的定义
//创建一个 User 对象
message User{
//list 对象
repeated Role roleList = 6;
}

3.2.3、Java String、Integer Map 在 protobuf 的定义
//创建一个 User 对象
message User{
// 定义简单的 Map string
map<string, int32> intMap = 7;
// 定义复杂的 Map 对象
map<string, string> stringMap = 8;
}

3.2.4、Java 对象 Map 在 protobuf 的定义
//创建一个 User 对象
message User{
// 定义复杂的 Map 对象
map<string, MapVauleObject> mapObject = 8;
}

// 定义 Map 的 value 对象
message MapVauleObject {
string code = 1;
string name = 2;
}

3.2.5、Java 实体类中嵌套实体 在 protobuf 的定义
//创建一个 User 对象
message User{
// 对象
NickName nickName = 4;
}

// 定义一个新的Name对象
message NickName {
string nickName = 1;
}

4、定义 proto 的一个属性
看完上面的,你应该明白要怎么定义一个 proto 对象的属性

格式如下
字段规则(可选) 字段类型 字段名称 字段标识符 字段默认值(可选)
例如:

4.1、一个相当于 Java 中的 String类型
Java 实体类写法
private String name;

proto 写法
string name = 1;

4.2、一个相当于 Java 中的 list 类型
Java 实体类写法
private List list;

proto 写法
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、生成 proto 对象
选中 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;
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);

}
}

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;

/**

    • 注意:
    • 该实现无法处理含有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;
          }
          }
          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.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());
}

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
Last modification:January 24, 2022
如果觉得我的文章对你有用,请随意赞赏