一、简介
我们之前学习了grpc的一些理论知识,现在我们开始正式进入编程环节。
我们的项目结构和之前的thrift结构还是一样的,一个common,一个client,一个server。只不过在grpc这里common它一般叫做api模块。还是放置一些公共的实体类,业务service类之类的。
二、项目结构
1. xxxx-api 模块 定义 protobuf的idl语言 并且通过命令创建对应的通用代码,包括实体类和service,其他模块统一引入即可。
2. xxxx-server模块1. 实现api模块中定义的服务接口2. 发布gRPC服务 (创建服务端程序,提供对外服务)
3. xxxx-clien模块1. 创建服务端stub(代理)2. 基于代理(stub) RPC调用。
1、代码编写
先创建一个rpc-grpc-api模块。
1.1、创建proto文件
我们在模块目录下创建一个proto目录来管理我们的.proto文件。
然后在该目录下创建一个hello.proto文件用来编写idl语言。
// 定义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){}
}
然后我们就用protoc命令来翻译这个idl。
1.1、命令行生成IDL
如同之前thrift一样,我们在这里也是要先编写idl的部分。我们需要首先在.proto后缀的文件中编写protobuf的IDL。
然后使用protoc命令把proto文件中的IDL 转换成编程语言 ,我们之前已经安装过protobuf环境了,所以可以直接使用。
具体命令格式为
protoc --java_out=生成的java代码路径 依赖的.proto文件
如果你要翻译为go语言,那就是protoc -go_out ......
举个例子就是:
protoc --java_out=/src/java /src/proto/hello.proto
但是这种我们需要在终端操作,不太方便。还好idea的maven插件支持了这个能力。
1.2、maven插件支持
maven插件 进行protobuf IDL文件的编译,并把他放置IDEA具体位置。这个能力的支持来自于一个grpc-java开源项目,我们看到它对于maven很方便的支持了能力,我们先创建一个模块来引入这个maven配置。
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.levi</groupId><artifactId>rpc</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>rpc-grpc-api</artifactId><properties><java.version>11</java.version><grpc-netty-shaded.version>1.73.0</grpc-netty-shaded.version><grpc-protobuf.version>1.73.0</grpc-protobuf.version><grpc-stub.version>1.73.0</grpc-stub.version><annotations-api.version>6.0.53</annotations-api.version><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--grpc的http2就是通过netty实现的--><dependency><groupId>io.grpc</groupId><artifactId>grpc-netty-shaded</artifactId><version>${grpc-netty-shaded.version}</version><scope>runtime</scope></dependency><!--支持protobuf的序列化--><dependency><groupId>io.grpc</groupId><artifactId>grpc-protobuf</artifactId><version>${grpc-protobuf.version}</version></dependency><!--支持grpc代理--><dependency><groupId>io.grpc</groupId><artifactId>grpc-stub</artifactId><version>${grpc-stub.version}</version></dependency><dependency> <!-- necessary for Java 9+ --><groupId>org.apache.tomcat</groupId><artifactId>annotations-api</artifactId><version>${annotations-api.version}</version><scope>provided</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></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></project>
ok,至此我们完成了这个maven的配置,然后我们来测试一下效果。
我们来到idea的maven插件位置找到对应的goal。
然后先执行compile生成实体message,再执行compile-custom生成service。他会自己去扫这个目录下的proto文件然后生成的默认位置就是模块的target目录。然后把对应的生成类拷贝到我们的java类路径下。然后删除proto生成的文件即可。注意service和实体类不再一个目录下,但是都在generated-sources这个目录下。
而且生成的实体类名字为:HelloProto.java. 这个名字就是我们的那个外部类名字,这是他的规范。
生成的service类名字为:HelloServiceGrpc.java 这个名字就是我们的service名字加了一个Grpc,这是他的规范。
ok至此我们就完成了初始化类文件。后续我们就可以使用了,但是现在存在两个问题需要优化。
1、我们每次都得点两下maven插件,一个生成message,一个生成service才好,这样太麻烦了,我们实际上可以构建复合goal,可以在idea的maven插件组合多个gola,然后就能顺序执行了。这个我就不演示了。我还是执行两个命令吧,不太喜欢定制化。而且你可以用maven的组合命令执行,idea只是一个图形化了这个功能。
2、我们每次输出的java类都是在target目录下的。害得拷贝一次,我们可以在maven配置直接生成到我们要的目标java目录下。
我们在maven中添加outputDirectory配置。
<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></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>
然后我们删除掉现在的类,再执行一次。
我们发现service把前面的message覆盖了,第二次生成的结果会覆盖第一次,所以我们还要加一个配置。
<!--追加新的文件,而不是清除旧的文件-->
<clearOutputDirectory> false</clearOutputDirectory>
此时配置变成这样。
<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>
此时就没问题了,而且target也不生成了。ok。下面我们就可以正式开始编写rpc能力的业务代码了。
2、代码结构
我们现在已经完成了api模块的开发,而且我们也生成了对应的grpc的类。我们现在就来看看我们生成的这些类的内容。我们先来看实体消息message类。
2.1、实体类message
我们在定义message的时候,就说了我们的实体类是被管理在一个外部类也就是我们这里的HelloProto里面的,我们使用的时候是以内部类的形式使用的HelloProto.HelloRequest以及HelloProto.HelloResponse这样的形式使用的。其余的是一些构建类和方法,我们先不说。
2.2、服务类service
而我们的服务类定义的时候名字是HelloService,最终生成的结果类为HelloServiceGrpc,可见其实就是在后面加了一个Grpc,而且也是都是内部类的形式,我们主要使用的就是这些内部类。我们之前在thrift的时候使用grpc的业务类其实是要实现他的接口覆盖他的方法实现自己的业务。
在grpc这里我们要实现的那个自己的业务类其实是HelloServiceImplBase这个内部类,我们需要在他的bindService里面书写我们的业务内容。他的命名方式就是我们声明的HelloServiceI后面加了一个ImplBase,这个和当初thrift的IFace接口一样。
其余的那些以Stub结尾的类其实就是生成的客户端发起远程rpc调用的接口类,不同的stub表示不同的rpc方法。
HelloServiceFutureStub这一看就是异步调用,返回Future。
HelloServiceBlockingStub这一看就是阻塞调用。其余等等,我们后面操作的时候会看到的。
ok至此我们就完成了api模块的实现,下面我们来编写client和server模块的代码,实现基于grpc的一个rpc调用。