概述
本文简单介绍通过gradle
构建以protobuf
作为数据通讯格式的spring boot
服务。
protobuf简介
Protocol Buffers是Google出品的一种序列化数据结构的协议。和xml,json等通讯格式一样,支持夸语言。但protobuf更小,更快,更简单。官方声称,比xml格式小3-10倍,速度快20到100倍。
具体性能对比可以参考:https://github.com/eishay/jvm-serializers/wiki.
除了更快更小,protobuf的还有其他优点:
- 可以通过结构描述生成代码。专注于文档设计,自动生成对象模型。
- 兼容性好
当然,也有缺点,如二进制格式导致可读性差,必须配合描述文件.proto
。
本文不详细介绍语法格式,可见官网proto3及其翻译Protobuf3 语法指南
spring-boot-proto 示例
gradle 配置:
build.gradle
如下:
buildscript {
ext {
springBootVersion = '2.0.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
// protobuf-gradle-plugin
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
// add protobuf plugin
apply plugin: 'com.google.protobuf'
group = 'com.thoreau'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('com.google.protobuf:protobuf-java:3.4.0')
compile('com.googlecode.protobuf-java-format:protobuf-java-format:1.4')
compile 'com.squareup.okhttp3:okhttp:3.10.0'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
// pre-compiled protoc
protobuf {
// Configure the protoc executable
protoc {
// Download from repositories
artifact = 'com.google.protobuf:protoc:3.0.0'
// generated java files dir
// generatedFilesBaseDir = "$projectDir/gen"
}
}
clean {
delete protobuf.generatedFilesBaseDir
}
test {
reports {
junitXml.enabled = false
html.enabled = true
}
}
sourceSets{
main {
java {
srcDir 'src/main/java'
}
resources {
srcDir 'src/main/resources'
}
proto {
// In addition to the default 'src/main/proto'
srcDir 'src/main/proto'
}
}
test {
proto {
// In addition to the default 'src/test/proto'
srcDir 'src/test/proto'
}
java {
srcDir 'src/test/java'
}
resources {
srcDir 'src/main/resources'
}
}
}
spring boot 使用protobuf
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
// 使用 protobuf 作为消息协议(序列化)
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
// 配置restTeamplete 解析 protobuf(反序列化)
@Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Collections.singletonList(hmc));
}
}
proto文件
根据gradle的配置,在src/proto
目录下新建两个文件:
hobby.proto
syntax = "proto3";// 版本
package com.thoreau.protobuf.vo; // 命名空间
option java_package = "com.thoreau.protobuf.generated.vo";//生成java包名
option java_outer_classname = "HobbyProto"; //生成Java类名
option java_multiple_files = true;
message Hobby {
string name = 1;
int32 level =2;
}
user.proto
syntax = "proto3";
package com.thoreau.protobuf.vo;
import "com/thoreau/protobuf/vo/hobby.proto";// 导入上一个文件
option java_package = "com.thoreau.protobuf.generated.vo";
option java_outer_classname = "UserProto";
option java_multiple_files = true;
message User {
reserved "Person";// 保留标识符
string firstName = 1;
string lastName = 2;
string emailAddress = 3;
string homeAddress = 4;
repeated Hobby hobbies =5;
repeated Skill skills =6;
enum Skill {
GOLANG = 0;
PYTHON = 1;
JAVA = 2;
RUST = 3;
CPP = 4;
}
}
gradle 编译:
gradle build
编译后在build目录下对应包中生成如下文件:
Hobby.class
HobbyOrBuilder.class
HobbyProto.class
User.class
UserOrBuilder.class
UserProto.class
controller
@RestController
@RequestMapping("/user")
public class UserResource {
@GetMapping(produces = "application/x-protobuf")// 指定response的Content-Type(消息类型)
public User getPersonProto() {
return User.newBuilder()
.setFirstName("thoreau")
.setLastName("zz")
.setEmailAddress("thoreau@gmail.com")
.setHomeAddress("123 xxx Street")
.addHobbies(Hobby.newBuilder().setName("basketball").build())
.addHobbies(Hobby.newBuilder().setName("football").build())
.addSkills(User.Skill.JAVA)
.addSkills(User.Skill.GOLANG)
.build();
}
}
启动服务,访问127.0.0.1:8080/user
,可拿到protobuf的二进制消息。
测试
package com.thoreau.protobuf;
import com.googlecode.protobuf.format.JsonFormat;
import com.thoreau.protobuf.generated.vo.User;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException;
import java.io.InputStream;
/**
* 2018/3/23 13:55.
*
* @author zhaozhou
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Test
public void getUserTest() {
ResponseEntity<User> user = restTemplate.getForEntity("/user", User.class);
// assert
}
@Test
public void getUserJson() throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://127.0.0.1:" + port + "/user")
.build();
Response response = client.newCall(request).execute();
InputStream inputStream = null;
try {
inputStream = response.body().byteStream();
JsonFormat jsonFormat = new JsonFormat();
User user = User.parseFrom(inputStream);
// assert
System.out.println(jsonFormat.printToString(user));
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
}
总结
对于前后端的通讯,比如js 到Java,是否应该使用protobuf有待商榷。后端服务间rpc等调用,protobuf一定是不错的选择。spring cloud 微服务间调用使用http,就可以像上文spring boot的示例一样使用protobuf通讯和序列化。
参考文档:
https://developers.google.com/protocol-buffers/docs/proto3 https://dzone.com/articles/will-salesforce-kiss-the-mule-or-kill-the-mule https://auth0.com/blog/beating-json-performance-with-protobuf/ http://www.baeldung.com/spring-rest-api-with-protocol-buffers http://colobu.com/2017/03/16/Protobuf3-language-guide/ https://github.com/eishay/jvm-serializers/wiki