![[tips][Java] RMIチュートリアル](https://casualdevelopers.com/wp-content/uploads/2015/03/room-2559790_1920-700x300.jpg)
かなり久しぶりの投稿になりました。社畜なうのKeidです。20代なので油断していましたが、地味に体調を崩してしまったので、健康を意識するようになってきました。そろそろ、Next Step、転職か起業して、会社に縛られない自由なエンジニアライフを手にしたいところです。自由と時間がほしい!最近RMIのアプリケーションを作るお仕事があったので、ついでにチュートリアルを作ってみました。
見出し
RMIとは?
RMIとは、Remote Method Invocationの略で、異なるホスト間でJavaオブジェクトのメソッドを実行するための通信手段です。
チュートリアルを開始する前に、RMIの動きを簡単に説明しておきます。RMIの仕組みの詳細はOracleさんのサイトを見てください。
RMIアプリケーションのアクターは、「RMIサーバー」、「RMIクライアント」、「RMIレジストリ」で、クライアントとサーバーでやり取りするためのJavaオブジェクトを「リモートオブジェクト」と言います。RMIサーバーは起動時にリモートオブジェクトをRMIレジストリに登録(bind)し、RMIクライアントは起動時にRMIレジストリからリモートオブジェクトを取得(lookup)して、リモートオブジェクトのメソッドを実行する、というフローになります。
RMIレジストリの実行には、rmiregistryコマンドを使う方法とLocateRegistry#createRegistryを使う方法がありますので、このチュートリアルでは両方やってみましょう。それでは、Let’s coding !
リモートオブジェクトの作成
(1) リモートオブジェクトのインターフェースを作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.remote; import java.rmi.Remote; import java.rmi.RemoteException; /** * Remote object * @author keid */ public interface Messenger extends Remote{ /** * send client's message * @param message * @return client's message * @throws RemoteException */ public default String send(String message) throws RemoteException{ System.out.println("client says "" + message + ""."); return "send "" + message + "" to server."; } }
(2) リモートオブジェクトの実装を作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.remote; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; /** * Remote object implementation * @author keid */ public class MessengerImpl extends UnicastRemoteObject implements Messenger{ /** * constructor of UnicastRemoteObject * @throws RemoteException */ public MessengerImpl() throws RemoteException { super(); } /** * serial version */ private static final long serialVersionUID = 1L; }
RMIサーバーの作成
(1) RMIサーバーのインターフェースを作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.server; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.Naming; import java.rmi.RemoteException; import java.net.MalformedURLException; /** * Rmi server * @author keid */ public interface RmiServer{ /** * start server * @throws RemoteException * @throws MalformedURLException */ public void start() throws RemoteException, MalformedURLException; /** * start server via Registry * @param host * @param port * @param serverName * @param remoteObject * @throws RemoteException */ public default void start(String host, int port, String serverName, Remote remoteObject) throws RemoteException{ // rmi url String url = "rmi://"+ host + ":" + Integer.toString(port) +"/" + serverName; // bind remote object to rmiregistry System.out.println("bind "" + url + "" to rmiregistry"); Registry rmiregistry = LocateRegistry.createRegistry(port); rmiregistry.rebind(url, remoteObject); System.out.println("Rmi server starting..."); } /** * start server via rmiregistry process * @param host * @param port * @param serverName * @param remoteObject * @throws RemoteException * @throws MalformedURLException */ public default void startViaProcess(String host, int port, String serverName, Remote remoteObject) throws RemoteException, MalformedURLException{ // rmi url String url = "rmi://"+ host + ":" + Integer.toString(port) +"/" + serverName; // bind remote object to rmiregistry System.out.println("bind "" + url + "" to rmiregistry"); Naming.rebind(url, remoteObject); System.out.println("Rmi server starting..."); } }
(2) RMIサーバーの実装を作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.server; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.rmi.Remote; import java.rmi.RemoteException; import rmi.remote.Messenger; import rmi.remote.MessengerImpl; /** * Rmi server implementation * @author keid */ public class RmiServerImpl implements RmiServer{ private String host; private int port; private String serverName; private Remote remoteObject; public RmiServerImpl(String host, int port, String serverName, Remote remoteObject){ this.host = host; this.port = port; this.serverName = serverName; this.remoteObject = remoteObject; } @Override public void start() throws RemoteException, MalformedURLException{ //start(host, port, serverName, remoteObject); startViaProcess(host, port, serverName, remoteObject); } /** * server main program * @param args */ public static void main(String[] args){ try { //String host = "localhost"; String host = InetAddress.getLocalHost().getHostAddress(); int port = 1099; String serverName = "server"; Messenger messenger = new MessengerImpl(); RmiServer server = new RmiServerImpl(host, port, serverName, messenger); server.start(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
RMIクライアントの作成
(1) RMIクライアントのインターフェースを作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.client; import java.rmi.Remote; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.NotBoundException; import java.net.MalformedURLException; /** * Rmi client * @author keid */ public interface RmiClient { /** * lookup remote object * @return remote object * @throws RemoteException * @throws NotBoundException * @throws MalformedURLException */ public Remote lookup() throws RemoteException, NotBoundException, MalformedURLException; /** * lookup via Registry * @param host * @param port * @param serverName * @return remote object * @throws RemoteException * @throws NotBoundException */ public default Remote lookup(String host, int port, String serverName) throws RemoteException, NotBoundException{ // rmi url String url = "rmi://"+ host + ":" + Integer.toString(port) +"/" + serverName; // lookup remote object from rmiregistry System.out.println("lookup "" + url + "" from rmiregistry"); Registry rmiregistry = LocateRegistry.getRegistry(port); return rmiregistry.lookup(url); } /** * lookup via rmiregistry process * @param host * @param port * @param serverName * @return remote object * @throws RemoteException * @throws NotBoundException * @throws MalformedURLException */ public default Remote lookupViaProcess(String host, int port, String serverName) throws RemoteException, NotBoundException, MalformedURLException{ // rmi url String url = "rmi://"+ host + ":" + Integer.toString(port) +"/" + serverName; // lookup remote object from rmiregistry System.out.println("lookup "" + url + "" from rmiregistry"); return Naming.lookup(url); } }
(2) RMIクライアントの実装を作る。
/** * Copyright (c) 2015, Keid All rights reserved. */ package rmi.client; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import rmi.remote.Messenger; /** * Rmi client implementation * @author keid */ public class RmiClientImpl implements RmiClient{ private String host; private int port; private String serverName; public RmiClientImpl(String host, int port, String serverName){ this.host = host; this.port = port; this.serverName = serverName; } @Override public Remote lookup() throws RemoteException, NotBoundException, MalformedURLException{ //return lookup(host, port, serverName); return lookupViaProcess(host, port, serverName); } /** * client main program * @param args */ public static void main(String[] args){ String message = "Hey, Keid!"; try { //String host = "localhost"; String host = InetAddress.getLocalHost().getHostAddress(); int port = 1099; String serverName = "server"; RmiClient client = new RmiClientImpl(host, port, serverName); Messenger messenger = (Messenger) client.lookup(); System.out.println(messenger.send(message)); } catch (UnknownHostException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
rmiregistryコマンドによるRMIアプリケーション実行
(1) RMIコマンドを実行する。
ターミナルを開いて、以下のコマンドを実行します。ポートは1099にします。
$ rmiregistry 1099
コマンドを実行した時にリモートオブジェクトに対してjava.lang.ClassNotFoundExceptionが発生する場合があります。これは、リモートオブジェクトの場所が分からなかったことが原因なので、コマンド実行時にクラスパスを指定してあげればよいです。
クラスパスをcodebaseで指定する場合は以下のコマンドを実行します。
$ rmiregistry -J-Djava.rmi.server.codebase=file:///クラスパス 1099
クラスパスをCLASSPATHで指定する場合は以下のコマンドを実行します。
$ rmiregistry -J-cp -JC:クラスパス 1099
codebaseはJVMに対してロードするクラスの場所を教える時に使用します。リモートにあるクラスパスを指定することが可能です。(RMIサーバーとRMIクライアントが別のホストの場合)一方、CLASSPATHはローカルなcodebaseを指定していることになります。(RMIサーバーとRMIクライアントが同じホストの場合)
一応、rmiregistryプロセスの停止方法ですが、上記のコマンドで実行した場合は、「control+c」で停止できます。実行時に「&」をつけてバックグラウンドで実行した場合は、psコマンドとgrepコマンドを組み合わせて PIDを検索し、killコマンドで停止させます。例としては以下のようにします。
$ rmiregistry & $ ps -ax | grep rmiregistry 38247 ttys000 0:00.26 /usr/bin/rmiregistry 38254 ttys000 0:00.00 grep rmiregistry $ kill 38247
(2) RMIサーバーを起動する。
RmiServerImplをrunします。(eclipseでもjavaコマンドでもOK)
<RMIサーバーのコンソール>
bind "rmi://IPアドレス:1099/server" to rmiregistry Rmi server starting...
(3) RMIクライアントを起動する。
RmiClientImplをrunします。(eclipseでもjavaコマンドでもOK)
<RMIクライアントのコンソール>
lookup "rmi://IPアドレス:1099/server" from rmiregistry send "Hey, Keid!" to server.
<RMIサーバーのコンソール>
bind "rmi://IPアドレス:1099/server" to rmiregistry Rmi server starting... client says "Hey, Keid!".
LocateRegistry#createRegistryによるRMIアプリケーションの実行
(1) RMIサーバーとRMIクライアントの実装を変更する。
お気付きの方もいると思いますが、最初からこのパターンの実装も組み込んであるので、実装クラスを少し変更するだけで、実行できます。RMIサーバーおよびRMIクライアントの変更点は以下です。
<RmiServerImplの変更点>
@Override public void start() throws RemoteException, MalformedURLException{ start(host, port, serverName, remoteObject); //startViaProcess(host, port, serverName, remoteObject); }
<RmiClientImplの変更点>
@Override public Remote lookup() throws RemoteException, NotBoundException, MalformedURLException{ return lookup(host, port, serverName); //return lookupViaProcess(host, port, serverName); }
この変更で、rmiregistryコマンドの実行が不要になりました。
(2) RMIサーバーを起動する。
RmiServerImplをrunして、rmiregistryコマンドによるRMIアプリケーション実行と同じ表示になれば成功です。
(3) RMIクライアントを起動する。
RmiClientImplをrunして、rmiregistryコマンドによるRMIアプリケーション実行と同じ表示になれば成功です。
That’s all.
これで、RMIの基本はマスターできたと思います。RMIを実際に使う場面ではTomcatアプリケーション化することが多いと思うので、リモートオブジェクトの破棄(UnicastRemoteObject#unexportObject)には注意しましょう。今回は不要です。
Tumblerでソースコード多めの記事をまとめるのはしんどいですね。だから、更新頻度が上がらないのかも。ところで、ソースコードのインターフェースにやたらとdefaultを使っていますが、ただ単にJava8で導入されたので使ってみただけです(笑)
<環境>
OS : OS X Yosemite 10.10.2
Java : 1.8.0_31


コメントを残す