一、背景说明
项目中使用到了Protobuf,但是因为不知道怎么使用它,所以看起来很高大上,现在写一个简单的Demo来实现基于RestTemplate的模拟客户端与服务端的远程调用!
二、实操代码
2.1 定义依赖POM文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<!-- Protobuf 依赖 -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.19.4</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.19.4</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
<!-- 网络请求依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.15</version>
<optional>true</optional>
</dependency>
</dependencies>
2.2 定义protobuf-maven-plugin
该配置先使用os-maven-plugin插件,该插件主要是为了识别不同的操作系统,这样插件可以根据不同的平台加载不同protoc编译器文件。google的protobuf团队也是用该插件来识别protoc的运行操作系统信息
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!-- protoc安装位置 -->
<protocExecutable>
D:\Project\protobuf\protoc-3.19.4-win64\bin\protoc.exe
</protocExecutable>
<pluginId>protoc-java</pluginId>
<!-- proto文件放置的目录 -->
<protoSourceRoot>${project.basedir}/src/main/resources/proto</protoSourceRoot>
<!-- 生成文件的目录 -->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<!-- 生成文件前是否把目标目录清空, 这个最好设置为false, 以免误删项目文件 -->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
</plugin>
2.3 定义BurtProtoController
@RestController
public class BurtProtoController {
private final Logger logger = LoggerFactory.getLogger(BurtProtoController.class);
@Resource
private RestTemplate restTemplate;
@PostMapping(value = "/protobuf/message", consumes = "application/x-protobuf", produces = "application/x-protobuf")
public BurtProto.Response message(HttpEntity<BurtProto.Request> requestPayload) {
BurtProto.Request request = requestPayload.getBody();
logger.info("Received following request: \n{}", request);
BurtProto.Resource requestResource = BurtProtoParser.extractResource(request.getResourcesList(), "AAAAA");
logger.info("BurtProto.Resource: AAAAA: \n{}", requestResource);
BurtProto.Resource responseResource = BurtProto.Resource.newBuilder()
.setChannelId(requestResource.getChannelId())
.setValue("Successful")
.build();
return BurtProto.Response.newBuilder()
.addResources(responseResource)
.build();
}
@GetMapping(value = "/helloProtoBuf")
public BurtProto.Response test() {
String url = "http://127.0.0.1:8080/protobuf/message";
BurtProto.Resource responseResource000 = BurtProto.Resource.newBuilder()
.setChannelId("AAAAA")
.setValue("AAAAA ChannelId Request")
.build();
BurtProto.Resource responseResource001 = BurtProto.Resource.newBuilder()
.setChannelId("BBBBB")
.setValue("BBBBB ChannelId Request")
.build();
BurtProto.Request request = BurtProto.Request.newBuilder()
.addAllResources(Lists.newArrayList(responseResource000, responseResource001))
.build();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "application/x-protobuf");
HttpEntity<BurtProto.Request> httpEntity = new HttpEntity<>(request, headers);
final ResponseEntity<BurtProto.Response> responseEntity = restTemplate.postForEntity(url, httpEntity, BurtProto.Response.class);
logger.info("ResponseEntity: \n{}", responseEntity.getBody());
return responseEntity.getBody();
}
}
2.4 定义BurtProto
syntax = "proto3";
package com.demo.protobuf;
option java_package = "com.demo.protobuf.proto";
option java_outer_classname = "BurtProto";
message Request {
repeated Resource resources = 1;
}
message Response {
repeated Resource resources = 1;
}
message Resource {
string channelId = 1;
string value = 2;
}
这个由IDEA生成即可,参考文章
【项目实战】Protobuf入门介绍以及如何生成proto对象文件
2.5 定义MessageProtoParser
public class MessageProtoParser {
private MessageProtoParser() {
}
public static MessageProto.Resource extractResource(List<MessageProto.Resource> resources, String channelId) {
return resources.stream()
.filter(res -> Objects.equals(channelId, res.getChannelId()))
.findFirst()
.orElse(null);
}
}
2.6 添加protobuf序列化支持
@EnableWebMvc
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(protobufHttpMessageConverter());
converters.add(mappingJackson2HttpMessageConverter());
}
/**
* protobuf 序列化
*/
@Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
/**
* protobuf 反序列化
*/
@Bean
public RestTemplate restTemplate(ProtobufHttpMessageConverter protobufHttpMessageConverter) {
return new RestTemplate(Lists.newArrayList(protobufHttpMessageConverter));
}
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter();
}
}
三、功能验证
看到响应体的内容返回为protobuf格式的内容。说明完成了一次请求与交互
注意Content-Type是:application/x-protobuf
这个格式是APIFOX不支持的格式哦、
以下是控制台中所有的输出,
2023-03-09 23:04:25.925 INFO 26644 --- [nio-8080-exec-2] c.d.p.controller.BurtProtoController : Received following request:
resources {
channelId: "AAAAA"
value: "AAAAA ChannelId Request"
}
resources {
channelId: "BBBBB"
value: "BBBBB ChannelId Request"
}
2023-03-09 23:04:25.926 INFO 26644 --- [nio-8080-exec-2] c.d.p.controller.BurtProtoController : BurtProto.Resource: AAAAA:
channelId: "AAAAA"
value: "AAAAA ChannelId Request"
2023-03-09 23:04:25.928 INFO 26644 --- [nio-8080-exec-3] c.d.p.controller.BurtProtoController : ResponseEntity:
resources {
channelId: "AAAAA"
value: "Successful"
}
解释一下,A发送给B一段内容,然后B将其打印出来,内容如下
resources {
channelId: "AAAAA"
value: "AAAAA ChannelId Request"
}
resources {
channelId: "BBBBB"
value: "BBBBB ChannelId Request"
}
B调用了BurtProtoParser下的extractResource方法,使用了流操作,并且只取了第一条数据findFirst(),并将其打印出来
2023-03-09 23:04:25.926 INFO 26644 --- [nio-8080-exec-2] c.d.p.controller.BurtProtoController : BurtProto.Resource: AAAAA:
channelId: "AAAAA"
value: "AAAAA ChannelId Request"
B将内容从“AAAAA ChannelId Request”改成了“Successful” 并且变成了响应体,传回给了A。A收到响应体的内容,并将其打印
2023-03-09 23:04:25.928 INFO 26644 --- [nio-8080-exec-3] c.d.p.controller.BurtProtoController : ResponseEntity:
resources {
channelId: "AAAAA"
value: "Successful"
}