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. syntax
で proto3
にしておかないと、古いバージョンが使われてしまう。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
ファイルを元にクラスを自動生成する必要がある。これは、maven
に dependency
と plugin
を設定する必要がある。
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
で、自動でクラスが生成されるようになる。
ちなみに IntelliJ にプロジェクトを作成する場合は、一旦 target
を生成したのちに実施するほうがよさそうだ。デフォルトのままだと、target
ディレクトリは表示されなくなってしまう。target
を作った後に、プロジェクトをintelliJ に読み込むと、自動で設定してくれる。そうでない場合は下記の設定 File > Project Structure Module を設定する必要がある。
尚、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
の挙動を明確に理解したい。