0


SpringBoot整合gRPC——入门级应用(详细注释)

创建一个maven项目,在父工程中创建api模块,client模块,server模块。

3d4dbfbd76af8deb1782186ad2983b1a.png

父工程中引入基本的依赖

<?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>

    <groupId>org.example</groupId>
    <artifactId>gRPCDemo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>api</module>
        <module>client</module>
        <module>service</module>
    </modules>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.7.3</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

api模块——定义proto文件

api模块负责用来存放定于gRPC服务的.proto文件,在使用前,要先引入依赖和protobuf-maven-plugin插件。protobuf-maven-plugin插件引入后,maven会帮助我们去下载protoc编译器来把.proto文件编译为我们所需要的java文件。

注意:这里的依赖和插件的版本的对应关系要设置好,否则就可能会报错protoc did not exit cleanly,下面提供了一个可以使用的依赖和插件版本。

<?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">
    <parent>
        <artifactId>gRPCDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api</artifactId>

    <properties>
        <grpc.version>1.6.1</grpc.version>
        <protobuf.version>3.3.0</protobuf.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>${grpc.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>

        <plugins>
            <!--           引入一些插件来帮助我们将.proto文件编译为java的类 -->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                    <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <!--                    使用的protoc版本,os.detected.classifier表示检测到的操作系统,这里检测到的是windows-x86_64-->
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                    </pluginArtifact>
                    <!--proto文件所在文件夹的位置-->
                    <protoSourceRoot>src/main/proto</protoSourceRoot>
                    <!--生成的文件的存放位置 -->
                    <outputDirectory>src/main/java</outputDirectory>
<!--                    在调用插件去生成java类时,是否清空输出文件夹,这个要设置为false,否则运行compile-custom时会把compile的结果给删了-->
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <!--                做的一个扩展,在执行maven的compile的时候,顺便也执行protobuf-maven-plugin插件的compile和compile-custom-->
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

然后在src/main目录下创建proto文件夹(这个目录在上面的插件中配置了),创建一个HelloWorldService.proto文件,如果没有提示的话,可以去下载idea的protobuf的插件。

//使用proto3语法
syntax = "proto3";

//生成多个java文件
option java_multiple_files = true;
//把生成的文件放到哪个包下
option java_package = "com.yue";
//输出的类名
option java_outer_classname = "HelloWorldServiceProto";

//定义一个类
service HelloWorldService {
  //定义一个gRPC方法,参数为HelloRequest,返回结果为HelloResponse
  rpc helloWorld(HelloRequest) returns(HelloResponse) {};
}
//定义的一个请求参数对象
message HelloRequest {
  //msg参数,编号为1,这是编号不是赋值
  string msg = 1;
  //code参数,编号为2
  int32 code=2;
}
//定义的一个返回结果对象
message HelloResponse {
  //resut参数,编号为1
  string result = 1;
}

然后我们就可以使用插件去将该proto文件编译成我们所需要的java文件了。先后双击compile和compile-custom即可。(这里也可以直接使用maven去compile该api模块,这个我们在上面的插件配置的扩展那里定义了)

4cf04ec8fde7903b384c4017981f736a.png

然后就可以得到这些文件。

979bda8e1a6852e7654fe10c4dde89e1.png

server模块——服务提供端

server模块作为服务提供端,要引入下列依赖

<?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">
    <parent>
        <artifactId>gRPCDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service</artifactId>

    <dependencies>
<!--        引入api模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        引入gRPC服务提供端依赖-->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>2.14.0.RELEASE</version>
        </dependency>
    </dependencies>

</project>

application.yml文件

注意这里的grpc.server.address,后续我们对client提供gRPC服务时,server所提供的调用的端口好是9081,不是8081

server:
  port: 8081 #正常的SpringBoot应用监听的端口号

grpc:
  server:
    port: 9081 #gRPC服务监听的端口号

正常的启动类

package com.yue;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class,args);
        log.info("service启动成功");
    }
}

然后我们去实现我们之前在api模块中定义的gRPC服务

package com.yue.server;

import com.yue.HelloRequest;
import com.yue.HelloResponse;
import com.yue.HelloWorldServiceGrpc;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;

/**
 * gRPC服务提供类,继承api模块中的proto文件编译生成的java文件,重写所定义的gRPC方法
 */
@GrpcService
@Slf4j
public class HelloWorldService extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
    /**
     * 定义的gRPC方法
     *
     * @param request          请求对象
     * @param responseObserver
     */
    @Override
    public void helloWorld(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        //解析请求,获取其中的参数,这些都是我们之前在proto文件中定义的
        String msg = request.getMsg();
        int code = request.getCode();
        log.info("请求中的参数为msg:{},code:{}", msg, code);
        //创建一个响应对象
        HelloResponse helloResponse = HelloResponse.newBuilder().setResult("我是server服务端,我收到了你的请求~").build();
        //将该响应对象返回给调用者
        responseObserver.onNext(helloResponse);
        //这次调用结束了
        responseObserver.onCompleted();
    }
}

server端的结构图为

25c52fb3b119f2b3d34fb06d92c9c1e7.png

client端——服务调用者

引入依赖

<?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">
    <parent>
        <artifactId>gRPCDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>client</artifactId>

    <dependencies>
        <!--        引入api模块-->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--        引入gRPC客户端依赖-->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.14.0.RELEASE</version>
        </dependency>
    </dependencies>

</project>

application.yml

server:
  port: 8080 #springboot应用监听的端口号

grpc:
  client:
    #在这里自定义服务提供方的地址,9081是服务提供方的gRPC监听的端口号
    #后续我会再写一篇使用zookeeper作为服务注册中心的
    grpc-server:
      address: localhost:9081
      negotiation-type: plaintext # 使用明文传输

启动类

package com.yue;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class ClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class,args);
        log.info("client启动成功");
    }
}

测试服务调用(阻塞方式和异步方式)

package com.yue.controller;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.yue.HelloRequest;
import com.yue.HelloResponse;
import com.yue.HelloWorldServiceGrpc;
import lombok.extern.slf4j.Slf4j;
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;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 测试gRPC服务调用
 */
@RestController
@Slf4j
@RequestMapping("/client")
public class TestController {
    //注入阻塞型的gRPC调用对象,服务调用的地址在application.yml中设置了
    @GrpcClient("grpc-server")
    private HelloWorldServiceGrpc.HelloWorldServiceBlockingStub blockingStub;
    //注入异步调用的gRPC调用对象
    @GrpcClient("grpc-server")
    private HelloWorldServiceGrpc.HelloWorldServiceFutureStub futureStub;
    //创建一个线程池来进行异步调用
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20,
            0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), new ThreadPoolExecutor.CallerRunsPolicy());
    //用来接收异步调用结果
    private String result;

    /**
     * 测试阻塞调用
     *
     * @return 调用结果
     */
    @GetMapping("/block")
    public String block() {
        //构造请求对象
        HelloRequest helloRequest = HelloRequest.newBuilder().setMsg("block").setCode(100).build();
        //进行阻塞式地调用
        HelloResponse helloResponse = blockingStub.helloWorld(helloRequest);
        return helloResponse.getResult();
    }

    /**
     * 测试异步调用
     *
     * @return 调用结果
     */
    @GetMapping("/future")
    public String future() throws InterruptedException {
        //构造请求对象
        HelloRequest helloRequest = HelloRequest.newBuilder().setMsg("block").setCode(100).build();
        //进行异步调用,看到这里返回的是一个ListenableFuture,大家应该都知道要怎么做了哈哈哈
        ListenableFuture<HelloResponse> helloResponseListenableFuture = futureStub.helloWorld(helloRequest);
        //创建一个CountDownLatch,来等待所有的异步任务完成(如果要执行多个异步任务的话,这里只是用一下)
        //参数为要等待执行的异步任务数,这里是1,其实就是一个计算器
        CountDownLatch countDownLatch = new CountDownLatch(1);
        //设置回调
        Futures.addCallback(helloResponseListenableFuture,
                new FutureCallback<HelloResponse>() {
                    @Override
                    public void onSuccess(HelloResponse helloResponse) {
                        log.info("异步调用成功了,结果为{}", helloResponse.getResult());
                        result = helloResponse.getResult();
                        //计数器减1,表示该异步任务执行完成
                        countDownLatch.countDown();
                    }

                    @Override
                    public void onFailure(Throwable throwable) {
                        log.error("异步调用失败,原因是{}", throwable.getMessage());
                    }
                },
                executor);
        //为了更直观地表现出异步任务,这里打印一个日志
        log.info("这里是主线程");
        //等待所有异步任务执行完成
        countDownLatch.await();
        return result;
    }
}

客户端的结构为

57613e2d1aaefdc2ab13619190b7b6d8.png

测试

测试阻塞调用,访问localhost:8080/client/block

9ddba32f687f6c83616d67dd589f66db.png

服务端的控制台

4710eee48ad8e2eca24ad36495aff409.png

测试异步调用

访问localhost:8080/client/future

7db68e1e0d15a3eabd4fdc08c4f5e8fe.png

server控制台

a7bc2ccf2881b2c13f2a4486676b5d4b.png

client控制台,可以看到,确实是进行了异步调用。

76d708fb1bce239a2ae0a4a5d2b802dd.png

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/weixin_53306150/article/details/141269898
版权归原作者 彼岸的野草 所有, 如有侵权,请联系我们删除。

“SpringBoot整合gRPC——入门级应用(详细注释)”的评论:

还没有评论