【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

  1. 定义服务调用接口程序(rpc-interface),一个普通工程
  2. 创建server端Springboot服务(rpc- server),依赖接口程序
  3. 创建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" }

猜你喜欢

转载自blog.csdn.net/smallbirdnq/article/details/134942114