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/

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