gRPC Java示例

1.什么是gRPC?

gRPC是一个高性能,开源通用RPC框架。默认情况下,它使用协议缓冲区来定义公开的服务。

该框架提供了双向流等功能,并支持许多不同的编程语言

gRPC最初由Google开发,现已获得Apache 2.0的许可。

为了展示gRPC的工作原理,让我们构建一个客户端和相应的服务器,以公开简单的Hello World gRPC服务。

2.使用协议缓冲区定义服务

gRPC服务是使用协议缓冲区定义的。这些是Google的语言无关,平台无关的可扩展机制,用于序列化结构化数据。

通过在.proto文件中定义协议缓冲区消息类型,您可以指定要序列化的信息的结构。每个协议缓冲区消息都是一个小的逻辑信息记录,其中包含一系列名称-值对

对于此示例,我们定义了一条包含有关Person的信息的第一条消息,以及一条包含Greeting的第二条消息。然后,这两者都在sayHello() RPC方法中使用,该方法从客户端获取人员消息并从服务器返回问候语。

除了包名称和一个选项(用于为不同的类生成单独的文件)之外,我们还定义了所使用的协议缓冲区语言的版本(proto3)。

有关更多信息,请参见协议缓冲区语言指南

下面的协议缓冲区文件存储在src / main / proto / HelloWorld.proto中。

syntax = "proto3";

option java_multiple_files = true;
package com.codenotfound.grpc.helloworld;

message Person {
  string first_name = 1;
  string last_name = 2;
}

message Greeting {
  string message = 1;
}

service HelloWorldService {
  rpc sayHello (Person) returns (Greeting);
}

现在我们已经定义了数据的结构,我们需要生成源代码,使我们能够使用Java轻松编写和读取protobuf消息。我们将在下一部分中使用Maven插件进行此操作。

3.常规项目设置

我们将使用以下工具/框架:

  • gRPC 1.16
  • Spring Boot 2.1
  • Maven的3.5

我们的项目具有以下目录结构:

4. Maven安装

我们使用Maven构建并运行示例。如果尚未安装,请确保下载并安装Apache Maven

下面显示的是POM文件中Maven项目的XML表示形式。它包含编译和运行示例所需的依赖关系。

为了配置和公开Hello World gRPC服务端点,我们将使用Spring Boot项目。

为了方便管理不同的Spring依赖项,使用了Spring Boot Starters。这些是一组方便的依赖项描述符,您可以将其包含在应用程序中。

我们包含的spring-boot-starter-web依赖项会自动设置一个嵌入式Apache Tomcat,它将托管我们的gRPC服务端点。

spring-boot-starter-test包括用于包括图书馆测试春天启动的应用程序的依赖关系的JUnitHamcrest的Mockito

用于gRPC框架Spring boot starter可以自动配置并运行具有@GRpcService启用Bean的嵌入式gRPC服务器,作为Spring Boot应用程序的一部分。入门程序同时支持Spring Boot版本1.5.X和2.XX,我们通过包含grpc-spring-boot-starter依赖项来启用它。

协议缓冲区支持多种编程语言生成的代码。本教程重点介绍Java。

生成基于protobuf的代码有多种方法,在本示例中,我们将使用grpc-java GitHub页面上记录的protobuf-maven-plugin

我们还包括os-maven-plugin扩展,该扩展生成各种有用的依赖于平台的项目属性。由于协议缓冲区编译器是本机代码,因此需要此信息。换句话说,protobuf-maven-plugin需要为运行平台的平台获取正确的编译器。

最后,插件部分包括spring-boot-maven-plugin。这使我们可以构建一个可运行的uber-jar。这是执行和传输代码的便捷方式。此外,该插件还允许我们通过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>

    <groupId>com.codenotfound</groupId>
    <artifactId>grpc</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>grpc-java-hello-world</name>
    <description>gRPC Java Example</description>
    <url>https://codenotfound.com/grpc-java-example.html</url>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <grpc-spring-boot-starter.version>3.0.0</grpc-spring-boot-starter.version>
        <os-maven-plugin.version>1.6.1</os-maven-plugin.version>
        <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.lognet</groupId>
            <artifactId>grpc-spring-boot-starter</artifactId>
            <version>${grpc-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>${os-maven-plugin.version}</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-maven-plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

protobuf-maven-plugin将生成的Java工件HelloWorld.proto位于文件的src / main /原/(这是默认位置的插件使用)。

执行以下Maven命令,应在target / generation-sources / protobuf /下生成不同的消息和服务类。

mvn compile

5. Spring Boot设置

创建一个SpringGrpcApplication包含一个main()方法的方法,该方法使用Spring Boot的SpringApplication.run()方法来引导应用程序,从而启动Spring。

请注意,@SpringBootApplication是一个方便的注释,补充说:[@Configuration](https://my.oschina.net/pointdance)@EnableAutoConfiguration,和@ComponentScan

有关Spring Boot的更多信息,请参阅Spring Boot入门指南

package com.codenotfound;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringGrpcApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringGrpcApplication.class, args);
  }
}

6.创建服务器

服务实现在HelloWorldServiceImplPOJO中定义,该POJO实现HelloWorldServiceImplBase从HelloWorld.proto文件生成的类。

我们重写该sayHello()方法,并Greeting根据Person请求中传递的名字和姓氏生成响应。

请注意,响应是一个StreamObserver对象。换句话说,服务默认是异步的。如要在收到响应时是否阻止,则由客户端决定,我们将在下面进一步介绍。

我们使用响应观察者的onNext()方法返回Greeting,然后调用响应观察者的onCompleted()方法来告诉gRPC我们已经完成了响应的编写。

HelloWorldServiceImplPOJO标注有@GRpcService其自动配置指定的服务GRPC到端口暴露6565。

package com.codenotfound.grpc;

import com.codenotfound.grpc.helloworld.Greeting;
import com.codenotfound.grpc.helloworld.HelloWorldServiceGrpc;
import com.codenotfound.grpc.helloworld.Person;
import io.grpc.stub.StreamObserver;
import org.lognet.springboot.grpc.GRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//6.创建服务器
@GRpcService
public class HelloWorldServiceImpl extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(HelloWorldServiceImpl.class);

    @Override
    public void sayHello(Person request,
                         StreamObserver<Greeting> responseObserver) {
        LOGGER.info("server received {}", request);

        String message = "Hello " + request.getFirstName() + " "
                + request.getLastName() + "!";
        Greeting greeting =
                Greeting.newBuilder().setMessage(message).build();
        LOGGER.info("server responded {}", greeting);

        responseObserver.onNext(greeting);
        responseObserver.onCompleted();
    }
}

7.创建客户端

客户端代码在HelloWorldClient类中指定。

@Component如果启用了自动组件扫描,我们将为客户端添加注释,这将使Spring自动在bean下面创建并导入到容器中(将@SpringBootApplication注释添加到主SpringWsApplication中等同于using @ComponentScan)。

要调用gRPC服务方法,我们首先需要创建一个存根。

存根有两种类型:

  • 一个阻塞/同步存根,将等待服务器响应
  • 一个非阻塞/异步存根,它对服务器进行非阻塞调用,在该服务器上异步返回响应。

在此示例中,我们将实现一个阻塞存根。

为了传输消息,gRPC使用http / 2及其之间的一些抽象层。这种复杂性隐藏在MessageChannel处理连通性的a后面。一般建议是每个应用程序使用一个通道,并在服务存根之间共享它。

我们使用带有init()注释的方法@PostConstruct,以便MessageChannel在bean初始化之后立即构建一个新的。然后使用该通道创建helloWorldServiceBlockingStub存根。

默认情况下,gRPC使用安全连接机制,例如TLS。因为这是一个简单的开发测试usePlaintext(),所以可以避免必须配置不同的安全工件(例如密钥/信任存储)。

sayHello()方法使用构建器模式创建一个Person对象,在该模式下我们设置了“ firstname”和“ lastname”输入参数。

helloWorldServiceBlockingStub则用来发送走向世界您好GRPC服务的请求。结果是一个Greeting对象,我们从中返回包含消息。

package com.codenotfound.grpc;

import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.codenotfound.grpc.helloworld.Greeting;
import com.codenotfound.grpc.helloworld.HelloWorldServiceGrpc;
import com.codenotfound.grpc.helloworld.Person;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

@Component
public class HelloWorldClient {

    private static final Logger LOGGER =
            LoggerFactory.getLogger(HelloWorldClient.class);

    private HelloWorldServiceGrpc.HelloWorldServiceBlockingStub helloWorldServiceBlockingStub;

    @PostConstruct
    private void init() {
        ManagedChannel managedChannel = ManagedChannelBuilder
                .forAddress("localhost", 6565).usePlaintext().build();
        helloWorldServiceBlockingStub =
                HelloWorldServiceGrpc.newBlockingStub(managedChannel);
    }

    public String sayHello(String firstName, String lastName) {
        Person person = Person.newBuilder().setFirstName(firstName)
                .setLastName(lastName).build();
        LOGGER.info("client sending {}", person);

        Greeting greeting =
                helloWorldServiceBlockingStub.sayHello(person);
        LOGGER.info("client received {}", greeting);

        return greeting.getMessage();
    }
}

8. gRPC Java测试1

最后,我们创建一个基本的单元测试用例,其中上述客户端用于向gRPC Hello World服务端点发送请求。然后,我们验证响应是否等于预期的问候。

Spring Boot 1.4引入@RunWith@SpringBootTest测试注释用于告诉JUnit使用Spring的测试支持运行,并使用Spring Boot的支持进行引导。

HelloWorldClientBean是自动连线,所以我们可以在测试情况下使用它。服务本身由@SpringBootTest注释自动启动。

剩下要做的就是使用assert语句将接收到的结果与预期的问候消息进行比较。

import static org.assertj.core.api.Assertions.assertThat;

import com.codenotfound.SpringGrpcApplication;
import com.codenotfound.grpc.HelloWorldClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes={SpringGrpcApplication.class})// 指定启动类
public class SpringGrpcApplicationTests {

    @Autowired
    private HelloWorldClient helloWorldClient;

    @Test
    public void testSayHello() {
        assertThat(helloWorldClient.sayHello("John", "Doe"))
                .isEqualTo("Hello John Doe!");
    }
}

运行上面的测试用例。在项目根文件夹中打开命令提示符,然后执行以下Maven命令:

mvn test

报错扩展: idea2020 版本Springboot项目测试例子无法启动 https://my.oschina.net/u/3730149/blog/4951641

结果应该是成功的构建,在此过程中,将启动gRPC服务器并调用Hello World服务。

2021-02-09 18:59:31.594  INFO 29788 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : Starting gRPC Server ...
2021-02-09 18:59:31.628  INFO 29788 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : 'com.codenotfound.grpc.HelloWorldServiceImpl' serv
ice has been registered.
2021-02-09 18:59:32.571  INFO 29788 --- [           main] o.l.springboot.grpc.GRpcServerRunner     : gRPC Server started, listening on port 6565.
2021-02-09 18:59:32.701  INFO 29788 --- [           main] com.codenotfound.grpc.HelloWorldClient   : client sending first_name: "John"
last_name: "Doe"

2021-02-09 18:59:32.908  INFO 29788 --- [ault-executor-0] c.c.grpc.HelloWorldServiceImpl           : server received first_name: "John"
last_name: "Doe"

2021-02-09 18:59:32.909  INFO 29788 --- [ault-executor-0] c.c.grpc.HelloWorldServiceImpl           : server responded message: "Hello John Doe!"

2021-02-09 18:59:32.915  INFO 29788 --- [           main] com.codenotfound.grpc.HelloWorldClient   : client received message: "Hello John Doe!"

如果您只想启动Spring Boot以使服务端点启动并运行,请执行以下Maven命令:

mvn spring-boot:run

8. gRPC Java测试2

写个controller

package com.codenotfound.grpc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class Controller {

    @Autowired
    HelloWorldClient helloWorldClient;

    @RequestMapping
    public String hello() {
        String s = helloWorldClient.sayHello("John", "Doe");
        System.out.println(s);
        return s;
    }
}

打包spring boot项目

java -jar target\grpc-1.0.jar 启动

访问:http://localhost:8080/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章