Kotlinを使ったWebアプリケーションサーバーを作成している時に、サーバーを分散できるように、複数モジュールでの通信を行おうと考えました。
KotlinはJVMで動いており、Javaの資産は全て使えます。
つまり、JVMでのORB/RPCであるRMIも使えるということになります。
基本はJavaと同じですがメモ書き程度にやったことを残しておきます。
構成
Projectは、以下の3つのモジュールに分割されています。
- RMI Interface
- RMI Server
- RMI Client
RMI Client及びRMI Serverは共にRMI Interfaceに依存しており、RMI ClientとRMI Serverにおける直接の依存関係はありません。
RMI Interface
RMI Interfaceモジュールは、下記のExampleRMIInterfaceのみを持ちます。
import java.rmi.Remote
import java.rmi.RemoteException
interface ExampleRMIInterface: Remote {
@Throws(RemoteException::class)
fun echo(text: String): String
}
RMIに用いるInterfaceは全てjava.rmi.Remoteを継承します。
また、RMIに用いるInterfaceの全てのメソッドはthrows RemoteException宣言されている必要があります。
Kotlinにおいては、@Throwsアノテーションをつけることで、コンパイル時にthrows宣言と同等の扱いになります。
RMI Server
RMI Serverは、ExampleRMIInterfaceの実装であるExampleRMIImplと、メインクラスとなるExampleRMIServer.ktを持ちます。
class ExampleRMIImpl: ExampleRMIInterface, UnicastRemoteObject(23456) {
override fun echo(text: String): String {
println(text)
return text
}
}
RMIに用いるInterfaceの実装には、@Throwsアノテーションをつける必要はありません。
また、UnicastRemoteObjectを継承することで、exportする手間を省くことができます。
UnicastRemoteObjectのコンストラクタに入れている数値はポート番号で、省略した場合匿名ポートが使われます。
import java.rmi.registry.LocateRegistry
fun main(args: Array<String>) {
val registry = LocateRegistry.createRegistry(23450)
val remote = ExampleRMIImpl()
registry.bind("example", remote)
}
まず、LocateRegistry.createRegistry()でRMIのRegistryを作成します。
このコードは、rmiregistryコマンドを実行することの完全な代替で、示す挙動はほぼ同じです。
独立したプロセスで行われるか、Server内の1スレッドとして行われるかの違いです。
また、引数はRegistryのポート番号で、クライアント側はこのポート番号を指定することでRegistryを取得できます。
そして、registry.bind()でRegistryにRemoteインターフェースを実装したオブジェクトを登録します。
第一引数は識別名で、LocateRegistryからこの識別名を用いてRMIのオブジェクトを取得できます。
RMI Client
RMI Clientは、RMIを用いてメソッドを呼び出す、メイン関数/クラスのみを持ちます。
fun main(args: Array<String>) {
val registry = LocateRegistry.getRegistry(23450)
val example = registry.lookup("example") as ExampleRMIInterface
example.echo("Hello, RMI!")
}
まず、ポートを指定してLocateRegistryを取得します。
同一物理サーバー内でない場合、第一引数にRegistryのホストのアドレスを文字列で入れることで、対象を指定できます。
次に、LocateRegistryに登録されているRemoteオブジェクトを取得し、使用しているRMI用のInterfaceにキャストします。
対象の検索はRegistryへの登録時に使った識別名を用います。
そして、取得したRemoteオブジェクトのメソッドを呼び出すと、RMI Serverへの通信が行われ、RMI Server側の実装が呼び出されます。
最後に
RMIについてわかりやすくまとめたドキュメントが少ない気がする(この記事がわかりやすいとは言っていない)
特に、rmiregistryを使わずにコードベースだけでやる方法は、中途半端だったりして情報を集めるのに多少苦労しました。
が、仕組みが分かってしまうと非常に簡単に実装することができ、通信をほとんど意識すること無く通信ができるので、非常に便利ですね。RMI。