SpringBoot集成gRPC
前一篇笔记中我们学习了在Java工程中使用gRPC的通用方式,这篇笔记我们学习如何在SpringBoot工程中集成gRPC。这里注意实际上gRPC官方没有对SpringBoot进行支持,这可能是出于商业竞争上考虑故意不提供SpringBoot支持,但gRPC集成到SpringBoot并不是什么复杂的功能,有很多完善的第三方Starter起步依赖供我们使用,我们也完全可以自己编写类似的起步依赖。
我们这里使用的是net.devh的grpc-server-spring-boot-starter。
官方文档:https://yidongnan.github.io/grpc-spring-boot-starter/zh-CN/
工程搭建
我们这里创建了3个Maven模块,其中rpc-api模块包含gRPC的IDL定义文件和protoc编译生成的代码,rpc-server和rpc-client分别是RPC调用的服务端和客户端,它们是两个SpringBoot工程。
|_rpc-api
|_rpc-client
|_rpc-service
rpc-api模块中我们直接引入gRPC依赖和编译IDL所需的Maven插件。
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.51.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.0</version>
</dependency>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
对于gRPC客户端和服务端SpringBoot工程,我们可以引入以下依赖:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
上面依赖适用于我们的工程模块既作为客户端也作为服务端的情况,如果我们的SpringBoot工程单独作为客户端或服务端,也可以直接使用grpc-client-spring-boot-starter或是grpc-server-spring-boot-starter。
rpc-api模块
rpc-api模块中,我们编写.proto定义文件。
demo_service.proto
syntax = "proto3";
option java_outer_classname = "DemoProtocol";
option java_package = "com.gacfox.demo.api";
option java_multiple_files = false;
message DemoReq {
string msg = 1;
}
message DemoRsp {
string msg = 1;
}
service DemoService {
rpc demo(DemoReq) returns (DemoRsp) {}
}
编写完成后,可以使用mvn compile对其进行编译以供服务端和客户端引用。
rpc-server模块
服务端工程中,我们需要在工程配置文件application.properties配置启动gRPC服务端的主机名和端口。
application.properties
spring.application.name=grpc-server
spring.main.web-application-type=none
grpc.server.address=*
grpc.server.port=9090
DemoService.java中,我们使用了注解@GrpcService标注了该类,这样grpc-spring-boot-starter就可以自动帮我们注册gRPC服务,其他写法和之前普通Java工程中使用gRPC相同。
DemoService.java
package com.gacfox.demo.rpcservice.service;
import com.gacfox.demo.api.DemoProtocol;
import com.gacfox.demo.api.DemoServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class DemoService extends DemoServiceGrpc.DemoServiceImplBase {
@Override
public void demo(DemoProtocol.DemoReq request, StreamObserver<DemoProtocol.DemoRsp> responseObserver) {
// ...具体业务处理
DemoProtocol.DemoRsp demoRsp = DemoProtocol.DemoRsp.newBuilder().setMsg("成功").build();
responseObserver.onNext(demoRsp);
responseObserver.onCompleted();
}
}
如果我们的gRPC服务端没有其它功能,我们可以指定如下配置不启动SpringMVC(Tomcat),避免占用8080端口和消耗额外的资源:
spring.main.web-application-type=none
rpc-client模块
客户端工程中我们需要在工程配置文件中配置服务端地址。
application.properties
spring.application.name=grpc-client
grpc.client.grpc-server.address=static://localhost:9090
grpc.client.grpc-server.negotiation-type=plaintext
其中,static://localhost:9090表示使用静态地址localhost:9090。实际开发中如果使用注册中心等方式,需要参考相关文档进行配置。
DemoController.java
package com.gacfox.demo.rpcclient.controller;
import com.gacfox.demo.api.DemoProtocol;
import com.gacfox.demo.api.DemoServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class DemoController {
@GrpcClient("grpc-server")
private DemoServiceGrpc.DemoServiceBlockingStub demoServiceBlockingStub;
@GetMapping("/test")
public String test() {
DemoProtocol.DemoReq demoReq = DemoProtocol.DemoReq.newBuilder()
.setMsg("Hello")
.build();
DemoProtocol.DemoRsp demoRsp = demoServiceBlockingStub.demo(demoReq);
System.out.println(demoRsp.getMsg());
return "success";
}
}
客户端代码中,我们注入Stub时,使用了@GrpcClient注解,注解需要一个服务名参数,该服务名需要和application.properties中定义的服务名一致,这里我们注入了接口的BlockingStub,后续代码就和之前章节没什么不同了。