写在前面
几经波折,最终进了一家公司实习,不过实习公司用的技术我都很陌生。但是的确,面试上没有要求这些技术。
办完了入职手续之后,就给我了个练习,给了我两个GitHub的链接,一个是protobuffer的,另一个是tars的,练习是:使用springboot写一个可以响应protobuffer的接口。
准备工作
因为都不知道protobuffer是什么东西,所以得先学习protobuffer。
protocol buffer是Google的一种独立的数据交换格式,可运用于多种领域。
protobuffer就是一种数据交换格式,和json、xml类似,但是比json、xml效率更加高,因为序列化之后更小,而且独立于语言、平台。缺点就是:编码后的数据可读性差。
protobuffer独立于语言和平台,需要protobuffer编译器的支持,所以需要安装protobuffer编译器。IDEA下有一个插件:GenProtobuf,这个插件可以自动的生成编译之后的Java类,不过也需要在Configure GenProtobuf中配置一下protobuf编译器。所以无论如何都需要安装protobuf的编译器,只不过在idea中安装了插件之后,不用再手动的去生成Java类了。
正式着手
mac本,使用IDEA工具,不过都其他系统、工具都大同小异。
第一步 创建工程
创建springboot工程,除了web模块之外,还需要引入protobuf的依赖
<!--protobuf的依赖-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.2</version>
</dependency>
然后补全项目结构
第二步 protobuf的准备
2.1 首先编写protobuf文件
例如:
// 表示是proto3的语法,proto3不能使用"required"
syntax = "proto3";
// 表示生成的序列化器的Java包
option java_package = "com.protobuf.protobuf.protobuf";
// 表示生成的Java序列化器的类名
option java_outer_classname = "Serializer";
// 表示一个消息
message Teacher{
// proto中的数据类型 字段名 = 唯一编号
// 编号范围 1-536870911,19000,19999两个不能使用 1-15编号占用一个字节,16-2047占用两个字节,常用字段应该使用1-15
int64 teacherId = 1;
int32 age = 2;
string name = 3;
}
2.2 使用插件生成Java类
例如:
第三步 编写接口
3.1 思路
这里是主要的实现逻辑,我的思路是:将一个实体序列化之后,再使用base64进行编码,编码成字符串,这个字符串就是我传输的对象。就像在json中,[1,2,3]表示一个数组一样。的确,protobuf编码之后的可读性很差,或者说根本没有可读性。在进行传输的时候,就将需要传输的内容放到请求体里,然后在接口中拿到这个字符串之后,先对字符串进行base64解码,然后再进行protobuf反序列化,将实例还原出来。需要响应protobuf格式的数据,那就先构造一个实例,然后使用protobuf进行序列化,在使用base64进行编码,将编码出来的字符串设置到响应体里。这样就完成了,请求和响应都使用了protobuf格式的数据。
3.2 代码
import com.ql.protobuftest.util.Serializer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Base64;
@RestController
@RequestMapping("/test")
public class ProtobufController {
@RequestMapping("/test1")
public String test1(HttpServletRequest request) {
//获取请求体里的字符串
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
try {
br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String requestBody = sb.toString();
byte[] decode = null;
try {
//使用base64解码读取到的字符串,
decode = Base64.getDecoder().decode(requestBody);
} catch (Exception e) {
e.printStackTrace();
}
Serializer.Teacher teacher;
try {
// 将解析出来的字符串进行反序列化
teacher = Serializer.Teacher.parseFrom(decode);
} catch (Exception e) {
// 如果解析失败,就返回一个特殊的实例
teacher = Serializer.Teacher.newBuilder().setName("-1").setTeacherId(-1).setAge(-1).build();
e.printStackTrace();
}
// 查看是否反序列化成功
System.err.println("teacher=" + teacher.toString());
// 返回一个序列化并且经过base64编码的字符串
Serializer.Teacher.Builder builder = Serializer.Teacher.newBuilder().setName("2").setAge(2).setTeacherId(2);
return Base64.getEncoder().encodeToString(builder.build().toByteArray());
}
}
第四步 测试
使用工具postman进行测试,对接口进行调用。
可以看到,请求体中携带了经过序列化并且使用base64编码的字符串,在接口中可以进行解析并且反序列化,将原本的对象还原回来。接口最后返回了一个将对象序列化之后并且经过base64编码的字符串。
ok 完成。我自己在写的时候,前面还写了好几个版本,但是都不太好,有问题。最终这版,我自己觉得还不错。