【Java】gRPC与Spring boot继承实现示例
上篇文章介绍了如何通过一个简单的maven项目实现gRPC客户端与服务端通信协议测试。但是这个只是一个普通的Java工程,我们在Java web开发的今天大多数Java程序员都是通过SpringCloud及SpringBoot开启微服务或者单个应用的开发,我们最好通过springboot继承gRPC,这样一个工程既可以支持web开发,也可以支持gRPC协议通信。庆幸的是有大佬yidongnan早为我们准备好了grpc相关starter,对应的开源代码地址如下:
https://github.com/yidongnan/grpc-spring-boot-starter
grpc-client-spring-boot-starter:client端start,实现自动注入stub的实现
grpc-client-spring-boot-autoconfigure:client端grpc Springboot配置文件自动加载注入
grpc-server-spring-boot-starter:server端启动starter,实现自动注入XXXServiceGrpc
grpc-server-spring-boot-autoconfigure:server端grpc springboot配置文件自动加载注入
这里是大佬yidongnan维护的项目:gRPC-Spring-Boot-Starter 文档及源码
gRPC-Spring-Boot-Starter 文档 | grpc-spring
具体server端实现和client端实现完全可以参考大佬的examples,完全足够让你理解其实现的便捷性,你也可以按照其参考实现,提供maven和gradle两种构建方式。如果你在看的过程中还有些许不懂,下面是我按照自己的理解把之前gRPC逻辑重新实现了一下,看这篇文章之前,建议先看一下我之前的一篇文章:
【JAVA】protobuf在Java中代码测试用例-CSDN博客
我们主要做以下工作
创建一个父项目(springboot-rpc),包括以下几个module
- 定义服务调用接口程序(rpc-interface),一个普通工程
- 创建server端Springboot服务(rpc- server),依赖接口程序
- 创建client端Springboot服务(rpc-client),依赖接口程序
一、创建父项目
1.父项目pom.xml,在springboot-rpc工程配置子module需要用到的maven依赖
<groupId>org.example</groupId>
<artifactId>springboot-rpc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>rpc-interface</module>
<module>rpc-server</module>
<module>rpc-client</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.59.1</grpc.version><!-- CURRENT_GRPC_VERSION -->
<protobuf.version>3.24.0</protobuf.version>
<protoc.version>3.24.0</protoc.version>
<!-- required for JDK 8 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
二、定义接口程序
1.定义接口定义程序rpc-interface maven依赖配置pom.xml
<parent>
<groupId>org.example</groupId>
<artifactId>springboot-rpc</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>rpc-interface</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- required for JDK 8 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<!--<version>1.59.1</version>-->
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<!--<version>1.59.1</version>-->
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<!-- Java 9+ compatibility - Do NOT update to 2.0.0 -->
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>test</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.basedir}/target/generated-sources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId>
<version>3.11.4</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<protocCommand>protoc</protocCommand><!-- brew install protobuf -->
<optimizeCodegen>true</optimizeCodegen>
<protocVersion>${protobuf.version}</protocVersion>
<includeStdTypes>true</includeStdTypes>
<!-- 导入其他proto文件
<includeDirectories>
<include>src/main/more_proto_imports</include>
</includeDirectories>
-->
<!--指定proto文件 -->
<inputDirectories>
<directory>src/main/protobuf</directory>
</inputDirectories>
<outputTargets>
<outputTarget>
<type>java</type>
</outputTarget>
<outputTarget>
<type>grpc-java</type>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.48.1</pluginArtifact>
</outputTarget>
</outputTargets>
<!--生成代码输出目录-->
<outputDirectory>src/main/java</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.定义proto文件
生成源码需要依赖proto文件,文件内容如下:
syntax="proto3";
option go_package="./;student"; //关于最后生成的go文件是处在哪个目录哪个包中,.代表在当前目录生成,student代表了生成的go文件的包名是student
option java_multiple_files = true; //表示下面的message需要编译成多个java文件
option java_package = "grpc.student"; //指定该proto文件编译成的java源文件的包名
option java_outer_classname = "StudentProto"; // 表示下面的message编译成的java类文件的名字
package student; // 定义作用域
service DemoService {
rpc Sender(StudentRequest) returns (StudentResponse){}
}
message StudentRequest {
string Id = 1;
}
message StudentResponse {
bytes result =1;
}
message Student {
int64 Id = 1;
string Name =2;
string No =3;
}
3.生成源码程序
依赖student.proto文件,执行编译生成源码文件,执行如下命令
mvn clean compile #当前rpc-interface工程执行
将生成的源码拷贝到src/main/grpc/student包下,工程结构如下:
rpc-interface
├── pom.xml
└── src
├── main
│?? ├── java
│?? │?? └── grpc
│?? │?? └── student
│?? │?? ├── DemoServiceGrpc.java
│?? │?? ├── Student.java
│?? │?? ├── StudentOrBuilder.java
│?? │?? ├── StudentProto.java
│?? │?? ├── StudentRequest.java
│?? │?? ├── StudentRequestOrBuilder.java
│?? │?? ├── StudentResponse.java
│?? │?? └── StudentResponseOrBuilder.java
│?? ├── protobuf
│?? │?? └── student.proto
│?? └── resources
三、创建gRPC Server服务
server端为了能依赖spring自动注入我们需要的Service实现,需要加入依赖grpc-server-spring-boot-starter,此starter自动根据springboot配置文件自动注入我们需要的service。
1.rpc-server pom.xml配置如下
<parent>
<groupId>org.example</groupId>
<artifactId>springboot-rpc</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>rpc-server</artifactId>
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
<!--
如果需要提供web服务可以加入此依赖,版本注意按照grpc-server-spring-boot-starter依赖的spring-boot版本保持一致即可。
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.16</version>
</dependency>
<!--
依赖接口定义
-->
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.rpc-server配置文件application.yml
spring:
application:
name: student-rpc-server #rpc client根据此名称配置
server:
servlet:
context-path: /
port: 9999
#grpc server端暴露的端口
grpc:
server:
port: 10005
3.编写server端的接口实现
在rpc- interface中定义的接口sender()方法需要在这里实现,并标注@GrpcService注解,代码如下:
@GrpcService
public class RpcServerImpl extends DemoServiceGrpc.DemoServiceImplBase {
private Logger log = LoggerFactory.getLogger(RpcServerImpl.class.getName());
@Override
public void sender(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {
if (Strings.isNullOrEmpty(request.getId())){
log.warn("request get param id is null");
return;
}
int id = Integer.parseInt(request.getId());
//build a student object then serialize it
// Student student = Student.newBuilder().build().;
Student.Builder builder = Student.newBuilder();
builder.setId(id);
builder.setName("easton");
builder.setNo("10001");
Student student = builder.build();
//try catch 属于测试序列化与反序列化代码块
ByteString jsonBs = null;
try {
//protobuf 序列化
String jsonStr = JsonFormat.printer().print(student);
log.info("json format:"+jsonStr);
jsonBs = ByteString.copyFromUtf8(jsonStr);
//反序列化
byte[] bytes = student.toByteArray();
Student student1 = Student.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
// ByteString bs = student.toByteString();
StudentResponse response = StudentResponse.newBuilder().setResult(jsonBs).build();
responseObserver.onNext(response);
//需要告诉客户端数据写完,否则客户端会一直等待数据回传结束
responseObserver.onCompleted();
log.info("server method run finish");
}
}
4.编写rpc-server启动类?
@SpringBootApplication
public class BootStrarpApplication {
public static void main(String[] args) {
SpringApplication.run(BootStrarpApplication.class,args);
}
}
四、创建gRPC client服务
创建完server端后接着编写客户端springboot服务
1.rpc-client pom.xml配置
跟服务端相对应需要引入依赖:grpc-client-spring-boot-starter,此starter会自动注入客户端stub实现,依赖配置为:
<parent>
<groupId>org.example</groupId>
<artifactId>springboot-rpc</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>rpc-client</artifactId>
<dependencies>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
<!--
web服务需要依赖此starter
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.16</version>
</dependency>
<!--
依赖接口定义
-->
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.rpc-client stub注入的代码调用实现
定义方法,此方法用来gRPC调用服务端sender()方法
@Service
public class StudentClientService {
//初始化student-rpc-server对应的stub,如果需要多个可以在这里注入
@GrpcClient("student-rpc-server")
private DemoServiceGrpc.DemoServiceBlockingStub blockingStub;
Logger logger = LoggerFactory.getLogger(StudentClientService.class);
public String sendToServer(int id){
logger.info("Will try to send " + id + " ...");
StudentRequest request = StudentRequest.newBuilder().setId(String.valueOf(id)).build();
StudentResponse response;
try{
response = blockingStub.sender(request);
}catch (StatusRuntimeException e){
e.printStackTrace();
logger.warn("RPC failed: {0}", e.getStatus());
return "";
}
ByteString byteString = response.getResult();
String result = byteString.toStringUtf8();
logger.info("Result: " +result);
return result;
}
}
3.rpc-client启动类
@SpringBootApplication
public class BootStrapApplication {
public static void main(String[] args) {
SpringApplication.run(BootStrapApplication.class,args);
}
}
4.rpc-client 配置文件application.yml配置
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: student-rpc-client
grpc:
client:
student-rpc-server: #服务名称不能写错,这个需要和server端spring.application.name的定义的名称一致,否默认的negotiationType:tls
address: 'static://localhost:10005'
# 是否开启保持连接(长连接)
enableKeepAlive: true
# 保持连接时长(默认20s)
keepAliveTimeout: 20s
# 没有RPC调用时是否保持连接(默认false,可禁用避免额外消耗CPU)
keepAliveWithoutCalls: false
# 客户端负载均衡策略(round_robin(默认), pick_first)
defaultLoadBalancingPolicy: round_robin
# 通信类型
# plaintext | plaintext_upgrade | tls
# 明文通信且http/2 | 明文通信且升级http/1.1为http/2 | 使用TLS(ALPN/NPN)通信
negotiationType: plaintext
# GLOBAL: 可以指定所有grpc通用配置
5.定义一个测试接口
由于rpc- client开启了web服务,为了模拟接口调用,以Restful协议请求rpc-client http接口,http接口调用grpc server服务,创建接口如下:
@RestController
public class DemoClientController {
@Autowired
private StudentClientService clientService;
@RequestMapping(value = "/getResult",method = RequestMethod.GET)
public String getResult(@RequestParam("id") String id){
return clientService.sendToServer(Integer.valueOf(id));
}
}
五、调用测试
1.启动服务端rpc-server
2.启动客户端rpc-client
3.通过浏览器URL请求测试:http://localhost:8080/getResult?id=111
{ "Id": "111", "Name": "easton", "No": "10001" }
?
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!