3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Java で gRPC サーバーを書くことによって Java 知識をアップデートする (1)

Posted at

Java を再びやることになったので、Java の知識をアップデートするために、gRPC サーバーを書いてみることにした。

こちらの記事が素晴らしいので、これを流しているだけなのだが、大量のヤクの毛刈りが発生しているので、それを含めて書いていきたい。

maven でコンソールアプリケーションを書く時のテンプレート

最初に詰まったのが、maven でとても単純な、Hello World レベルのコンソールアプリケーションを作成すること。単純に、maven-archetype-quickstart を使うと良い。

mvn archetype:generate 
  -DgroupId=com.simplearchitect \
  -DartifactId=helloGRPC \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false

proto file

まさに Hello World レベルの protobuf. syntaxproto3 にしておかないと、古いバージョンが使われてしまう。java_package で、自動生成されるクラスの、パッケージが決まるので、もうちょっと真面目な名前にしておいてもよかったかもしれない。outer_classnameはプロトコルの概念を表す自動生成クラスの名前になる。

下記のプロトコルバッファは、SayHello というメソッドを持ち、HelloRequest を送信すると、HelloReply が返ってくるシンプルなサービスを定義している。

main/proto/helloworld.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

こちらを読むと、proto ファイルをよりよく理解できる。

クラスの自動生成

proto ファイルを元にクラスを自動生成する必要がある。これは、mavendependencyplugin を設定する必要がある。
gRPC-Java - An RPC library and framework のトップページを見て、maven の設定を行う。

io.grpc 系のライブラリの追加と、protobuf-maven-plugin および os-maven-plugin を入れる必要がある。ちなみに、os-maven-plugin

os-maven-plugin is a Maven extension/plugin that generates various useful platform-dependent project properties normalized from ${os.name} and ${os.arch}.

とのこと。os-maven-plugin.

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.31.1</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
  <groupId>org.apache.tomcat</groupId>
  <artifactId>annotations-api</artifactId>
  <version>6.0.53</version>
  <scope>provided</scope>
</dependency>
  :
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.6.2</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.31.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

この設定により、mvn clean package で、自動でクラスが生成されるようになる。
image.png

ちなみに IntelliJ にプロジェクトを作成する場合は、一旦 target を生成したのちに実施するほうがよさそうだ。デフォルトのままだと、target ディレクトリは表示されなくなってしまう。target を作った後に、プロジェクトをintelliJ に読み込むと、自動で設定してくれる。そうでない場合は下記の設定 File > Project Structure Module を設定する必要がある。

image.png

尚、IntelliJ にロードするときは、プロジェクトを右クリックして、Mavenプロジェクトとして追加的なものを選んでおかないと、maven ではエラーは出なくとも、画面上 dependency が解決できず Error になってしまう。

gRPC サーバーを書く

生成されたクラスを参照してサーバーを書く。これを Main のプログラムから実行できるようにする。単純に、サーバーを起動して、設定して、継承したクラスをセットするだけになっている。

package com.simplearchitect;

import java.io.IOException;
import java.util.logging.Logger;

import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.CallStreamObserver;

import static io.grpc.ServerBuilder.*;

public class SimpleServer {
    Logger logger = Logger.getLogger(getClass().getName());

    Server server;

    public void start() throws IOException {
        server = ServerBuilder
                .forPort(8080)
                .addService((BindableService) new SimpleHelloServiceImpl())
                .build()
                .start();
        logger.info("start gRPC server.");
    }

    public void stop() {
        if (server != null) {
            server.shutdown();
            logger.info("Shutting down gRPC server.");
        }
    }

    public void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static class SimpleHelloServiceImpl extends GreeterImplBase {
        Logger logger = Logger.getLogger(getClass().getName());

        public void sayHello(HelloRequest request, CallStreamObserver<HelloReply> responseObserver) {
            logger.info(String.format("request: name = %s", request.getName()));
            HelloReply reply = HelloReply.newBuilder().setMessage("Hello, " + request.getName()).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

abstract static class

不思議なことに、static クラスを new している。これはどういうことだろうか? 下記のクラスが継承元のクラスだが、static abstract class になっている。これは、実際は、static クラスではなく、このクラスは、インナークラスになっており、インナークラスの親のクラスをインスタンス化していなくても、インスタンス化できることを示すため、static がついているので、実態はインスタンス化できるクラスである。

public static abstract class GreeterImplBase implements io.grpc.BindableService {

    /**
     * <pre>
     * Sends a greeting
     * </pre>
     */
    public void sayHello(io.grpc.examples.helloworld.HelloRequest request,
        io.grpc.stub.StreamObserver<io.grpc.examples.helloworld.HelloReply> responseObserver) {
      asyncUnimplementedUnaryCall(getSayHelloMethod(), responseObserver);
    }

    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
          .addMethod(
            getSayHelloMethod(),
            asyncUnaryCall(
              new MethodHandlers<
                io.grpc.examples.helloworld.HelloRequest,
                io.grpc.examples.helloworld.HelloReply>(
                  this, METHODID_SAY_HELLO)))
          .build();
    }
  }

Java への Dependency の追加

mvn clean package で jar ファイルを作って実行するとエラーになる。Jar にライブラリが含まれていないためだ。maven-assembly-plugin を追加しよう。これで、mainClass も指定できて、依存関係にある jar を含んだライブラリを作ってくれる。
`

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>com.simplearchitect.App</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>sample</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

The Assembly Plugin for Maven enables developers to combine project output into a single distributable archive that also contains dependencies, modules, site documentation, and other files.

うむ。まさに求めていたもの。

実行

PS > > java -jar .\target\helloGRPC-1.0-SNAPSHOT-jar-with-dependencies.jar
Aug 16, 2020 11:17:33 PM com.simplearchitect.SimpleServer start
INFO: start gRPC server.
> Enter stop.

ちゃんと動くかしらんけど、まずは、コンパイルされて、ライブラリ付きの jar ができて、動くところまでは出来た。次回はクライアントを作ってみて、動かしてみる。CallStreamObserver の挙動を明確に理解したい。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?