spring-boot 使用protobuf
2018-03-24 [spring-boot](http://thoreauz.com/tags/spring-boot/) [protobuf](http://thoreauz.com/tags/protobuf/) [spring](http://thoreauz.com/tags/spring/)
概述
本文简单介绍通过gradle构建以protobuf作为数据通讯格式的spring boot服务。
protobuf简介
Protocol Buffers是Google出品的一种序列化数据结构的协议。和xml,json等通讯格式一样,支持夸语言。但protobuf更小,更快,更简单。官方声称,比xml格式小3-10倍,速度快20到100倍。
具体性能对比可以参考:https://github.com/eishay/jvm-serializers/wiki.
除了更快更小,protobuf的还有其他优点:
1. 可以通过结构描述生成代码。专注于文档设计,自动生成对象模型。
2. 兼容性好
当然,也有缺点,如二进制格式导致可读性差,必须配合描述文件.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-pluginclasspath '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 pluginapply plugin: 'com.google.protobuf'group = 'com.thoreau'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8repositories {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 protocprotobuf {// Configure the protoc executableprotoc {// Download from repositoriesartifact = 'com.google.protobuf:protoc:3.0.0'// generated java files dir// generatedFilesBaseDir = "$projectDir/gen"}}clean {delete protobuf.generatedFilesBaseDir}test {reports {junitXml.enabled = falsehtml.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
@SpringBootApplicationpublic class Application {public static void main(String[] args) {SpringApplication.run(Application.class);}// 使用 protobuf 作为消息协议(序列化)@BeanProtobufHttpMessageConverter protobufHttpMessageConverter() {return new ProtobufHttpMessageConverter();}// 配置restTeamplete 解析 protobuf(反序列化)@BeanRestTemplate 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.classHobbyOrBuilder.classHobbyProto.classUser.classUserOrBuilder.classUserProto.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 {@LocalServerPortprivate int port;@Autowiredprivate TestRestTemplate restTemplate;@Testpublic void getUserTest() {ResponseEntity<User> user = restTemplate.getForEntity("/user", User.class);// assert}@Testpublic 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);// assertSystem.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
