之前一直用Go来写gRPC的服务,简单而直接。 最近工作涉及用Java开发几个gRPC的服务,我们知道Java中有很多框架和Magical,所以这几个服务还是希望以Java的风格来写,因此打算写几篇文章对此做个整理。本篇做为开篇 ,先简单从快速开始一个项目原型开始写起。

开发环境

  • JDK 1.8
  • gradle 3.5
  • gRPC 1.30
  • protobuf 3.2.0

初始化项目原型

新建项目目录grpc-java-demo

1mkdir grpc-java-demo

使用gradle生成项目原型:

1cd grpc-java-demo
2gradle init --type=java-application

修改生成构建脚本build.gradle:

 1apply plugin: 'java'
 2apply plugin: 'com.google.protobuf'
 3apply plugin: 'eclipse'
 4
 5buildscript {
 6  repositories {
 7    mavenCentral()
 8  }
 9  dependencies {
10    classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1'
11  }
12}
13
14repositories {
15   mavenCentral()
16}
17
18sourceSets {
19   main {
20   	java {
21   		srcDir 'build/generated/source/proto/main/java'
22   		srcDir 'build/generated/source/proto/main/grpc'
23   	}
24   }
25}
26
27compileJava {
28    sourceCompatibility=1.8
29    targetCompatibility=1.8
30    options.encoding='UTF-8'
31}
32compileTestJava {
33    sourceCompatibility=1.8
34    targetCompatibility=1.8
35    options.encoding='UTF-8'
36}
37
38configurations { 
39	all*.exclude group:'com.sun.xml.bind',module:'jaxb-impl'
40	all*.exclude group:'xml-apis',module:'xml-apis'
41	all*.exclude group:'stax',module:'stax-api'
42	all*.exclude group:'org.slf4j',module:'slf4j-log4j12'
43	all*.exclude group:'commons-logging'
44}
45
46ext {
47	grpcVersion= '1.3.0'
48	logbackVersion = '1.2.2'
49	slf4jVersion='1.7.25'
50}
51
52dependencies {
53    compile "io.grpc:grpc-netty:${grpcVersion}"
54    compile "io.grpc:grpc-protobuf:${grpcVersion}"
55    compile "io.grpc:grpc-stub:${grpcVersion}"
56    
57    compile "org.slf4j:slf4j-api:$slf4jVersion"
58    compile "ch.qos.logback:logback-classic:$logbackVersion"
59    runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
60    runtime "org.slf4j:log4j-over-slf4j:$slf4jVersion"
61    runtime "org.slf4j:jul-to-slf4j:$slf4jVersion"
62    testCompile 'junit:junit:4.12'
63}
64
65protobuf {
66  protoc {
67    artifact = 'com.google.protobuf:protoc:3.2.0'
68  }
69  plugins {
70    grpc {
71      artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
72    }
73  }
74  generateProtoTasks {
75    all()*.plugins {
76      grpc {
77        option 'enable_deprecated=false'
78      }
79    }
80  }
81}
82
83tasks.eclipse.dependsOn compileJava

新建src/main/resources目录,在目录下创建logback的配置文件logback.xml:

 1<?xml version="1.0" encoding="UTF-8"?>
 2
 3<configuration debug="false" scan="false" scanPeriod="30 seconds">
 4
 5  <contextName>grpc-java-demo</contextName>
 6
 7  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 8    <encoder>
 9      <pattern>
10        <![CDATA[
11        [%date{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %level %logger - %message%n
12        ]]>
13      </pattern>
14    </encoder>
15  </appender>
16
17  <logger name="com.frognew.grpc.demo" level="INFO"  additivity="false">
18  	<appender-ref ref="STDOUT" />
19  </logger>
20
21  <root level="WARN">
22    <appender-ref ref="STDOUT" />
23  </root>
24
25</configuration>

删除src/main/java下的App.java文件和src/test/java下的AppTest.java文件。 在src下新建名称为proto的源码目录,在此目录中新建helloworld.proto文件内容如下:

 1syntax = "proto3";
 2
 3option java_multiple_files = true;
 4option java_package = "com.frognew.grpc.demo.helloworld";
 5option java_outer_classname = "HelloWorldProto";
 6option objc_class_prefix = "HLW";
 7
 8package helloworld;
 9
10service Greeter {
11  rpc SayHello (HelloRequest) returns (HelloReply) {}
12}
13
14message HelloRequest {
15  string name = 1;
16}
17
18message HelloReply {
19  string message = 1;
20}

运行gradle eclipse生成eclipse项目,此过程中会调用gradle的com.google.protobuf插件基于src/main/proto中的PB文件生成Java代码。 将生成的项目导入eclipse中。

grpc-java-eclipse-project

后续开发过程中,当PB文件发生修改时,只需要执行gradle eclipse再刷新eclipse项目即可。

HelloWorldServer和HelloWorldClient

基于官方demo中的代码进行修改:

com.frognew.grpc.demo.helloworld.HelloWorldServer.java:

 1package com.frognew.grpc.demo.helloworld;
 2
 3import java.io.IOException;
 4
 5import org.slf4j.Logger;
 6import org.slf4j.LoggerFactory;
 7
 8import io.grpc.Server;
 9import io.grpc.ServerBuilder;
10import io.grpc.stub.StreamObserver;
11
12public class HelloWorldServer {
13
14	private static final Logger LOGGER = LoggerFactory.getLogger(HelloWorldServer.class);
15	
16	 private Server server;
17
18	  private void start() throws IOException {
19	    /* The port on which the server should run */
20	    int port = 50051;
21	    server = ServerBuilder.forPort(port)
22	        .addService(new GreeterImpl())
23	        .build()
24	        .start();
25	    LOGGER.info("Server started, listening on " + port);
26	    Runtime.getRuntime().addShutdownHook(new Thread() {
27	      @Override
28	      public void run() {
29	        // Use stderr here since the logger may have been reset by its JVM shutdown hook.
30	        System.err.println("*** shutting down gRPC server since JVM is shutting down");
31	        HelloWorldServer.this.stop();
32	        System.err.println("*** server shut down");
33	      }
34	    });
35	  }
36
37	  private void stop() {
38	    if (server != null) {
39	      server.shutdown();
40	    }
41	  }
42
43	  /**
44	   * Await termination on the main thread since the grpc library uses daemon threads.
45	   */
46	  private void blockUntilShutdown() throws InterruptedException {
47	    if (server != null) {
48	      server.awaitTermination();
49	    }
50	  }
51
52	  /**
53	   * Main launches the server from the command line.
54	   */
55	  public static void main(String[] args) throws IOException, InterruptedException {
56	    final HelloWorldServer server = new HelloWorldServer();
57	    server.start();
58	    server.blockUntilShutdown();
59	  }
60
61	  static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
62
63	    @Override
64	    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
65	      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
66	      responseObserver.onNext(reply);
67	      responseObserver.onCompleted();
68	    }
69	  }
70}

com.frognew.grpc.demo.helloworld.HelloWorldClient.java:

 1package com.frognew.grpc.demo.helloworld;
 2
 3import java.util.concurrent.TimeUnit;
 4
 5import org.slf4j.Logger;
 6import org.slf4j.LoggerFactory;
 7
 8import io.grpc.ManagedChannel;
 9import io.grpc.ManagedChannelBuilder;
10import io.grpc.StatusRuntimeException;
11
12public class HelloWorldClient {
13	private static final Logger LOGGER = LoggerFactory.getLogger(HelloWorldClient.class);
14	
15	private final ManagedChannel channel;
16	  private final GreeterGrpc.GreeterBlockingStub blockingStub;
17
18	  /** Construct client connecting to HelloWorld server at {@code host:port}. */
19	  public HelloWorldClient(String host, int port) {
20	    this(ManagedChannelBuilder.forAddress(host, port)
21	        // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
22	        // needing certificates.
23	        .usePlaintext(true));
24	  }
25
26	  /** Construct client for accessing RouteGuide server using the existing channel. */
27	  HelloWorldClient(ManagedChannelBuilder<?> channelBuilder) {
28	    channel = channelBuilder.build();
29	    blockingStub = GreeterGrpc.newBlockingStub(channel);
30	  }
31
32	  public void shutdown() throws InterruptedException {
33	    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
34	  }
35
36	  /** Say hello to server. */
37	  public void greet(String name) {
38	    LOGGER.info("Will try to greet " + name + " ...");
39	    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
40	    HelloReply response;
41	    try {
42	      response = blockingStub.sayHello(request);
43	    } catch (StatusRuntimeException e) {
44	      LOGGER.warn("RPC failed: {}", e.getStatus());
45	      return;
46	    }
47	    LOGGER.info("Greeting: " + response.getMessage());
48	  }
49
50	  /**
51	   * Greet server. If provided, the first element of {@code args} is the name to use in the
52	   * greeting.
53	   */
54	  public static void main(String[] args) throws Exception {
55	    HelloWorldClient client = new HelloWorldClient("localhost", 50051);
56	    try {
57	      /* Access a service running on the local machine on port 50051 */
58	      String user = "world";
59	      if (args.length > 0) {
60	        user = args[0]; /* Use the arg as the name to greet if provided */
61	      }
62	      client.greet(user);
63	    } finally {
64	      client.shutdown();
65	    }
66	  }
67}

运行Server输出:

1Server started, listening on 50051

运行Client,向Server发送RPC请求,输出:

1Will try to greet world ...
2Greeting: Hello world

总结

本篇主要是根据gRPC Java官方示例代码演示怎么快速使用Gradle快速创建gRPC Java项目的框架原型,因为gRPC HelloWorld的代码十分简单,就不展开多写了。

参考