一、简介
我们完成了关于grpc的一些基础编程,但是实际开发中往往要和一些主流的框架体系做整合,融入到里面去。我们常见的就是springboot体系,那么grpc如何在springboot中使用这个问题我们今天就来探讨一下。
二、分析springboot会封装哪些东西
我们说其实他除了哪些我们自己定义的东西,其他都能封装,我们能定义什么。
proto文件和proto文件生成的类。
实际上springboot也是这么干的。他通过我们定义出来的东西,然后你自己实现哪些Base接口。发布注册出去,至于如何发布,其实就是在你的xxxserviceImpl类上面加一个注解@GrpcService。这样就发布出去了,其余的什么端口暴露都是在配置文件中的。至于如何整合其实我们可以参考文档spring-grpc
三、开发服务端
我们重新开始,先创建一个springboot项目rpc-grpc-boot-server,借助idea的能力很快就创建出来了,我们选择springboot3 jdk17的版本。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除spring-boot-starter-tomcat的maven依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
</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.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!--com.google.protobuf:protoc这个命令是生成grpc的实体message的,os.detected.classifier是maven的一个内置系统参数,用来获取本机操作系统类型,
这里是获取本机的操作系统类型,然后根据操作系统类型来获取protoc的命令,无需你自己写死,这里maven会自己获取-->
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
<!--grpc-java-->
<pluginId>grpc-java</pluginId>
<!--io.grpc:protoc-gen-grpc-java这个命令是支持grpc的service服务的-->
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.73.0:exe:${os.detected.classifier}</pluginArtifact>
<!--basedir就是当前模块的根目录,我们直接生成到我们的java目录下面去-->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--追加新的文件,而不是清除旧的文件-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<!--goals是maven执行的命令,类似于clean或者install,这里是grpc插件下的两个命令,
compile是生成grpc的实体message的,compile-custom是生成grpc的service服务的,两个依次执行就能获得结果-->
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
我们引入简单的web,然后引入一个springboot对grpc支持的starter,grpc-server-spring-boot-starter
而且你看到我排除了tomcat的依赖,我们这里不需要tomcat,我们是grpc的服务端。不要引入tomcat,但是你在这里排除了其实springboot还是会给你启动一个,因为你只是排除了依赖。我们需要在配置文件中排除。
# 核心配置的 就是gRPC服务的端口号
spring:
application:
name: rpc-grpc-boot-server
# 关闭web应用,不启动tomcat这类web容器,我们走grpc服务端
main:
web-application-type: none
grpc:
server:
port: 9000
然后我们编写我们的proto文件,注意这个maven的grpc插件会扫描src/main下面的proto文件,所以你的proto应该放在这个下面。
// 定义proto文件版本号
syntax = "proto3";
// 生成一个java类即可
option java_multiple_files = false;
// 生成的java类的包名
option java_package = "com.levi";
// 外部类,这里就是HelloProto,实际开发你可以有多个proto管理不同业务类,然后各自的外部类都可以。比如OrderService就是Order.proto 外部类就是OrderProto
option java_outer_classname = "HelloProto";
// 定义请求接口参数
message HelloRequest{
string name = 1;
}
// 定义接口响应参数
message HelloResponse{
string result = 1;
}
// 定义服务
service HelloService{
/* 简单rpc,参数为HelloRequest类型,返回类型为HelloResponse */
rpc hello(HelloRequest) returns (HelloResponse){}
}
然后我们根据maven的插件生成对应的类即可。
然后我们编写自己的实现类。
package com.levi.rpcgrpcbootserver.service;
import com.levi.HelloProto;
import com.levi.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
//1.接受client的请求参数
String name = request.getName();
//2.业务处理
System.out.println("name parameter "+name);
//3.封装响应
//3.1 创建相应对象的构造者
HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();
//3.2 填充数据
builder.setResult("hello method invoke ok");
//3.3 封装响应
HelloProto.HelloResponse helloResponse = builder.build();
// 4. 响应client
responseObserver.onNext(helloResponse);
// 5. 响应完成
responseObserver.onCompleted();
}
}
至此我们完成了HelloServiceImpl并且你加上了@GrpcService注解就等于发布出去了,就等同于我们之前的代码
serverBuilder.addService(new HelloServiceImpl());
而且我们在配置文件中配置了grpc的端口,此时你会发现其实和我们之前没有springboot的时候server发布的要素就齐全了。其余的springboot都给你封装了。
然后我们启动服务。
我们的服务在9000的grpc端口启动,没有启动tomcat,一切ok。
注意,springboot的版本要和grpc-starter的版本对应,不然会启动失败。具体可以参考spring-grpc文档
四、开发客户端
客户端创建服务和服务端差不多这里就简化了。只是客户端需要引入一个grpc的客户端的starter,grpc-client-spring-boot-starter
<dependencies>
<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>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
</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.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<!--com.google.protobuf:protoc这个命令是生成grpc的实体message的,os.detected.classifier是maven的一个内置系统参数,用来获取本机操作系统类型,
这里是获取本机的操作系统类型,然后根据操作系统类型来获取protoc的命令,无需你自己写死,这里maven会自己获取-->
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}</protocArtifact>
<!--grpc-java-->
<pluginId>grpc-java</pluginId>
<!--io.grpc:protoc-gen-grpc-java这个命令是支持grpc的service服务的-->
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.73.0:exe:${os.detected.classifier}</pluginArtifact>
<!--basedir就是当前模块的根目录,我们直接生成到我们的java目录下面去-->
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<!--追加新的文件,而不是清除旧的文件-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<!--goals是maven执行的命令,类似于clean或者install,这里是grpc插件下的两个命令,
compile是生成grpc的实体message的,compile-custom是生成grpc的service服务的,两个依次执行就能获得结果-->
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意客户端我们没必要屏蔽tomcat,因为我们这里是调用者不提供服务,而且你还要在什么controller里面请求,还是需要mvc环境的。
其余的proto和服务端一样,你可以直接复制过来使用。实际开发我们是有一个common-api的模块让客户端和服务端一起引入的这里我没写,就两边各自维护一份吧。
然后编写yml文件
# 对应我们以前grpc的客户端开发的一些配置
spring:
application:
name: rpc-grpc-boot-client
server:
port: 8080
# 对应我们以前grpc的客户端开发的一些配置
grpc:
client:
grpc-server:
address: 'static://127.0.0.1:9000'
negotiation-type: plaintext
我们然后就可以编写我们的controller业务类,在里面发起客户端调用了,因为这里是springboot的整合,所以你还是要那套的。
package com.levi.rpcgrpcbootclient.controller;
import com.levi.HelloProto;
import com.levi.HelloServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
// 以grpc注解的形式注入客户端stub,可以选择阻塞的还是流的还是异步的啥的,看你自己,springboot风格
@GrpcClient("grpc-server")
private HelloServiceGrpc.HelloServiceBlockingStub stub;
@GetMapping("/hello")
public String hello() {
HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
builder.setName("levi");
HelloProto.HelloRequest helloRequest = builder.build();
HelloProto.HelloResponse helloResponse = stub.hello(helloRequest);
return helloResponse.getResult();
}
}
然后我们启动客户端,监听8080端口。
然后启动客户端。我们请求http://localhost:8080/hello
响应没问题即可。
我们其实可以看到客户端我们以前是这么写的。
public static void main(String[] args) {
//1.创建通信的管道
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
//2.获得代理对象 stub进行调用
try {
// 我们这里以阻塞的形式调用,也就是一直等返回值回来才往下走
HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel);
//3. 完成RPC调用
//3.1 准备参数
HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder();
builder.setName("hello");
HelloProto.HelloRequest helloRequest = builder.build();
//3.1 进行功能rpc调用,获取相应的内容,像本地调用那样调用远程服务
HelloProto.HelloResponse helloResponse = helloService.hello(helloRequest);
String result = helloResponse.getResult();
System.out.println("result = " + result);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
// 4. 关闭通道
managedChannel.shutdown();
}
}
现在你可以看到通过配置文件的封装其实有一部分我们已经不用写了,而且通过注入的方式stub也是可以不用new了,直接注入即可。其余的就是直接调即可。