- 1.什么是gRPC?
- 2.使用协议缓冲区定义服务
- 3. Maven安装
- 4. Spring Boot设置
- 5.创建服务端
- 6.创建客户端
- 7. gRPC Java测试
gRPC是一个高性能,开放源代码的通用RPC框架。默认情况下,它使用协议缓冲区来定义公开的服务。
该框架提供了双向流等功能,并支持许多不同的编程语言。
gRPC最初由Google开发,现已获得Apache 2.0的许可。
为了展示gRPC的工作原理,我们来构建一个客户端和相应的服务器,以公开一个简单的Hello World gRPC服务。
gRPC服务是使用协议缓冲区定义的。这些是Google的语言无关,平台无关的可扩展机制,用于序列化结构化数据。
通过在.proto文件中定义协议缓冲区消息类型,您可以指定要序列化的信息的结构。每个协议缓冲区消息都是一个小的逻辑信息记录,其中包含一系列名称-值对。
对于此示例,我们定义了一条包含有关Person的信息的第一条消息,以及一条包含Greeting的第二条消息。然后,这两者都在sayHello() RPC方法中使用,该方法从客户端获取人员消息并从服务器返回问候语。
除了包名称和一个选项(用于为不同的类生成单独的文件)之外,我们还定义了所使用的协议缓冲区语言的版本(proto3)。
有关更多信息,**请点击链接查看proto文件语法指南官方文档(需fq访问) **:协议缓冲区语言指南。
下面的协议缓冲区文件存储在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. 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包括用于包括图书馆测试春天启动的应用程序的依赖关系的JUnit,Hamcrest和的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命令启动示例。
4.0.0 com.codenotfound grpc-java-hello-world 0.0.1-SNAPSHOT jar grpc-java-hello-world gRPC Java Example https://codenotfound.com/grpc-java-example.html org.springframework.boot spring-boot-starter-parent 2.1.0.RELEASE UTF-8 UTF-8 1.8 3.0.0 1.6.1 0.6.1 org.springframework.boot spring-boot-starter-web io.github.lognet grpc-spring-boot-starter ${grpc-spring-boot-starter.version} org.springframework.boot spring-boot-starter-test test kr.motd.maven os-maven-plugin ${os-maven-plugin.version} org.springframework.boot spring-boot-maven-plugin org.xolstice.maven.plugins protobuf-maven-plugin ${protobuf-maven-plugin.version} com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier} compile compile-custom
在protobuf-maven-plugin将生成的Java工件HelloWorld.proto位于文件的src/main/proto/(这是默认位置的插件使用)。
使用maven插件,编译 proto 文件:
创建一个SpringGrpcApplication包含一个main()方法的方法,该方法使用Spring Boot的SpringApplication.run()方法来引导应用程序,从而启动Spring。
请注意,@SpringBootApplication是一个方便的注释,补充说:@Configuration,@EnableAutoConfiguration,和@ComponentScan。
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); } }5.创建服务端
服务实现在HelloWorldServiceImplPOJO中定义,该POJO实现HelloWorldServiceImplbase从HelloWorld.proto文件生成的类。
我们重写该sayHello()方法,并Greeting根据Person请求中传递的名字和姓氏生成响应。
请注意,响应是一个StreamObserver对象。换句话说,该服务在默认情况下是异步的。如要在接收响应时是否阻止,则由客户端决定,我们将在下面进一步介绍。
我们使用响应观察者的onNext()方法返回Greeting,然后调用响应观察者的onCompleted()方法来告知gRPC我们已经完成了响应的编写。
该HelloWorldServiceImplPOJO标注有@GRpcService其自动配置指定的服务GRPC到端口暴露6565。
package com.codenotfound.grpc; import org.lognet.springboot.grpc.GRpcService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codenotfound.grpc.helloworld.Greeting; import com.codenotfound.grpc.helloworld.HelloWorldServiceGrpc; import com.codenotfound.grpc.helloworld.Person; import io.grpc.stub.StreamObserver; @GRpcService public class HelloWorldServiceImpl extends HelloWorldServiceGrpc.HelloWorldServiceImplbase { private static final Logger LOGGER = LoggerFactory.getLogger(HelloWorldServiceImpl.class); @Override public void sayHello(Person request, StreamObserver6.创建客户端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(); } }
客户端代码在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(); } }7. gRPC Java测试
最后,我们创建一个基本的单元测试用例,其中上述客户端用于向gRPC Hello World服务端点发送请求。然后,我们验证响应是否等于预期的问候。
Spring Boot 1.4引入的@RunWith和@SpringBootTest测试注释用于告诉JUnit使用Spring的测试支持运行,并使用Spring Boot的支持进行引导。
该HelloWorldClientBean是自动连线,所以我们可以在测试情况下使用它。服务本身由@SpringBootTest注释自动启动。
剩下要做的就是使用assert语句将接收到的结果与预期的问候消息进行比较。
package com.codenotfound; import static org.assertj.core.api.Assertions.assertThat; 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; import com.codenotfound.grpc.HelloWorldClient; @RunWith(SpringRunner.class) @SpringBootTest public class SpringGrpcApplicationTests { @Autowired private HelloWorldClient helloWorldClient; @Test public void testSayHello() { assertThat(helloWorldClient.sayHello("Grpc", "Test")) .isEqualTo("Hello Grpc Test!"); } }