springBoot+protobuf(全程Protocol Buffers协议)简单入门

  1. 了解Protocol Buffers协议
    Protocal Buffers是google推出的一种序列化协议,用于结构化的数据序列化、反序列化。
    官方解释:Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法。可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
  2. 为什么要使用protobuf
    使用protobuf的原因肯定是为了解决开发中的一些问题,那使用其他的序列化机制会出现什么问题呢?

(1)java默认序列化机制:效率极低,而且还能不能跨语言之间共享数据。

(2)XML常用于与其他项目之间数据传输或者是共享数据,但是编码和解码会造成很大的性能损失。

(3)gson格式也是常见的一种,但是gson在解析的时候非常耗时,而且gson结构非常占内存。

但是我们protobuf是一种灵活的、高效的、自动化的序列化机制,可以有效的解决上面的问题。由于 protobuf是跨语言的,所以用不同的语言序列化对象后,生成一段字节码,之后可以其他任何语言反序列化并自用,大大方便了跨语言的通讯,同时也提高了效率

3.protobuf常见数据类型与Java对照表
在这里插入图片描述

  1. 语法类型讲解,创建user_login.proto文件
syntax = "proto3";//声明版本 syntax=”proto3”,如果没有声明,则默认是proto2.

option java_package = "com.test.proto";//指定生成的类应该放在什么Java包名下.
option java_outer_classname = "MessageUserLogin";//定义应该包含这个文件中所有类的类名,调用时MessageUserLogin.MessageUserLoginRequest 或者 MessageUserLogin.MessageUserLoginResponse.
option java_multiple_files = false;//生成的类是否应该放在一个单独的Java文件里,是否需要將生成的类拆分为多个.默认false,可以不写.

message MessageUserLoginRequest {//message对应java的class
  /** 字段编号
  在 Protocol Buffers (protobuf) 中,每个字段都必须有一个唯一的标识符,这个标识符是一个整数,称为字段编号(Field Number)。
  字段编号用于在序列化和反序列化过程中唯一地识别一个字段,即使在不同版本的协议中字段的顺序发生变化,只要字段编号不变,就可以保证数据的一致性和正确性。
  字段编号的范围是1到2^29-1,但是通常建议将字段编号保持在1到1500之间,以避免冲突。字段编号不能重复,否则编译器会报错。
  字段编号的选择也会影响序列化后的二进制数据的大小和性能,较小的字段编号会导致更小的二进制数据和更快的序列化/反序列化速度,但这种影响通常可以忽略不计。
  在实际开发中,我们通常根据字段的重要性和出现的频率来选择字段编号,例如将重要的、经常使用的字段编号设置得较小。
   */
  string a = 1;   //string 数据类型 a字段名 1字段编号
  int32 b = 2;
  int64 c = 3;
  bool d = 4;
  double e = 5;
  float f = 6;
  repeated string g = 7; //repeated 对应java的List集合 repeated string g = List<String> g
  map<string, string> h = 8;//map 对应java的Map集合
  repeated MessageUserLoginResponse i = 9;//集合对象 repeated MessageUserLoginResponse i = List<MessageUserLoginResponse> g
  map<string, MessageUserLoginResponse> j = 10;//map对象
  MessageUserLoginResponse k = 11;//嵌套对象字段
}

message MessageUserLoginResponse {
  string a = 1;
  int32 b = 2;
  int64 c = 3;
  bool d = 4;
  double e = 5;
  float f = 6;
}

  1. 如何将.proto文件生成java文件,目前简单介绍一个窗口生成的。idea工具生成后续追加
    下载protoc.exe编辑器
    下载链接: 下载地址
    根据同样的系统选择不同的编译器就ok,我用的windows, 所以选择 :protoc-3.11.0-win64.zip
    下载后只用到bin里面的protoc.exe
  2. SpringBoot使用protobuf格式的接口
    建立SpringBoot项目,pom.xml需要引用内容如下:
    根据自己项目实际情况添加,我的springBoot版本是2.7.17,springBoot版本这里比较重要,
    因为后边在模拟Content-Type=application/x-protobuf请求时,是要配置我们服务端能请求类型内容。
    Spring Boot 2.6及更高版本中,配置方式是不同的。
    根据自己项目实际情况添加
    根据自己项目实际情况添加
    根据自己项目实际情况添加
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.17</version>
        <relativePath/>
        <!--  lookup parent from repository  -->
    </parent>
    <!--  Generated by https://start.springboot.io  -->
    <!--  优质的 spring/boot/data/security/cloud 框架中文文档尽在 => https://springdoc.cn  -->
    <groupId>com.game.module</groupId>
    <artifactId>GameModule</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>GameModule</name>
    <description>GameModule</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--springBoot相关-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--springBoot相关-->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20240303</version>
            <!--  使用最新的版本或适合你项目的版本  -->
        </dependency>
        <!--protobuf相关-->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.protobuf-java-format</groupId>
            <artifactId>protobuf-java-format</artifactId>
            <version>1.2</version>
        </dependency>
        <!--protobuf相关-->
        <!-- 网络请求依赖 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4</version>
        </dependency>
        <!-- 网络请求依赖 -->
        <!-- 工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.0</version>
        </dependency>
        <!-- 工具类 -->
        <!-- websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!-- websocket -->
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <!-- lombok -->
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.2</version>
                <configuration>
                    <fork>true</fork>
                    <mainClass>com.game.module.gamemodule.GameModuleApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <!--  是否替换资源中的属性  -->
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>*</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

编写.proto文件,user_login.proto 内容如下:

syntax = "proto3";

option java_package = "com.boomsecret.protobuf";
option java_outer_classname = "MessageUserLogin";

message MessageUserLoginRequest {
    string username = 1;
    string password = 2;
}

message MessageUserLoginResponse {
    string access_token = 1;
    string username = 2;
}

  1. 生成java代码:
    我的做法是将下载好的protoc.exe放在项目里面了,这样我直接在项目路径下cmd进去好找位置,你也可以不放。
    在这里插入图片描述
    然后进入protoc.exe存方位cmd进去执行命令。
    示例: .\center\user_login.proto 是你的.proto文件所在路径 --java_out=./ 指定生成文件的路径
    大家根据实际情况自行修改,我的是在同一级所以.\user_login.proto
.\protoc.exe .\user_login.proto --java_out=./

在这里插入图片描述
在这里插入图片描述
强调下你的项目路径就是你.proto里面指定的option java_package = 路径
8.编写protobuf格式的Controller接口:

package com.example.protobuf.demo.controller;

import com.boomsecret.protobuf.MessageUserLogin;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import util.HttpUtils;

import java.net.URI;
import java.util.UUID;

@Controller
public class TestController {
    @RequestMapping(value = "/demo/test", produces = "application/x-protobuf")
    @ResponseBody
    public MessageUserLogin.MessageUserLoginResponse getPersonProto(@RequestBody MessageUserLogin.MessageUserLoginRequest request) {
        MessageUserLogin.MessageUserLoginResponse.Builder builder = MessageUserLogin.MessageUserLoginResponse.newBuilder();
        builder.setAccessToken(UUID.randomUUID().toString()+"_res");
        builder.setUsername(request.getUsername()+"_res");
        return builder.build();
    }

}

编写测试HttpUtils模拟application/x-protobuf

package util;

import com.google.protobuf.GeneratedMessageV3;
import com.googlecode.protobuf.format.JsonFormat;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.HttpClients;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class HttpUtils {

    public static HttpResponse doPost(HttpPost post, GeneratedMessageV3 message) throws IOException {
        HttpClient httpclient = HttpClients.createDefault();
        String requestUrl = post.getURI().toString();

        ByteArrayInputStream inputStream = new ByteArrayInputStream(message.toByteArray());
        InputStreamEntity inputStreamEntity = new InputStreamEntity(inputStream);
        post.setEntity(inputStreamEntity);

        post.addHeader("Content-Type", "application/x-protobuf");
        for (Header header : post.getAllHeaders()) {
            System.out.println(header.getName() + ":" + header.getValue());
        }

        StringBuilder sb = new StringBuilder();
        sb.append("curl -XPOST ");
        for (Header header : post.getAllHeaders()) {
            sb.append(" -H \"").append(header.getName()).append(":").append(header.getValue()).append("\"");
        }

        String jsonBody = JsonFormat.printToString(message);
        jsonBody = jsonBody.replace("\"", "\\\"");
        sb.append(" -d \"").append(jsonBody).append("\"");
        sb.append(" ").append(requestUrl);

        System.out.println(sb.toString());
        return httpclient.execute(post);
    }
}

编写测试接口

package com.game.module.gamemodule.controller;

import com.game.module.gamemodule.proto.MessageUserLogin;
import com.game.module.gamemodule.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;
import java.util.Arrays;
import java.util.UUID;

@Slf4j
@RestController
public class UserLoginController {

    @RequestMapping(value = "/demo/test", produces = "application/x-protobuf")
    @ResponseBody
    public MessageUserLogin.MessageUserLoginResponse getPersonProto(@RequestBody MessageUserLogin.MessageUserLoginRequest request) {
        MessageUserLogin.MessageUserLoginResponse.Builder builder = MessageUserLogin.MessageUserLoginResponse.newBuilder();
        String token = UUID.randomUUID().toString() + "_res";
        builder.setAccessToken(token);
        String name = request.getUsername() + "_res";
        builder.setUsername(name);
        return builder.build();//序列化
    }

    @RequestMapping(value = "/userTest")
    public void test() {
        try {
            URI uri = new URI("http", null, "localhost", 9101, "/demo/test", "", null);
            HttpPost request = new HttpPost(uri);
            MessageUserLogin.MessageUserLoginRequest.Builder builder = MessageUserLogin.MessageUserLoginRequest.newBuilder();
            builder.setUsername("tom");
            builder.setPassword("123456");
            log.info("序列化后的内容:{}", Arrays.toString(builder.build().toByteArray()));
            HttpResponse response = HttpUtils.doPost(request, builder.build());// builder.build() 序列化
            MessageUserLogin.MessageUserLoginResponse messageUserLoginResponse = MessageUserLogin.MessageUserLoginResponse.parseFrom(response.getEntity().getContent());//parseFrom 反序列化
            log.info("请求结果反序列化后:{}", messageUserLoginResponse.toString());
        } catch (Exception e) {
            System.out.println("请求异常");
        }
    }

}


以debug方式运行SpringBoot项目,并在controller加断点,然后运行测试代码: 访问http://localhost:9101/userTest 接口
在这里插入图片描述
此时我们会看到415,表示错误org.springframework.web.HttpMediaTypeNotSupportedException表明服务器不支持你的客户端尝试使用的application/x-protobuf;charset=UTF-8这种内容类型。这通常发生在服务器只处理特定的媒体类型,而application/x-protobuf不在这些类型中时。此时就要配置服务端支持protobuf,就与上面我们的springboot版本配置相关,我的springBoot版本是2.7.17。Spring Boot 2.6及更高版本中,配置方式是不同的。
添加MessageConverterConfig,2.6以上配置

package com.game.module.gamemodule.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;

@Configuration
public class MessageConverterConfig {
    @Bean
    public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }
}

2.6以下配置方式

   @Configuration
   public class WebConfig implements WebMvcConfigurer {

       @Override
       public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
           converters.add(new ProtobufHttpMessageConverter());
       }
   }

再次执行就可以了
在这里插入图片描述
可以看到请求过来的数据是正确的,放行后可以看到响应数据也是正确的:
在这里插入图片描述

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值