RMIの機能

概要

Spring RemoteにおけるRMIの機能を記述してみようかと思います。

RMIとは、他のサーバにあるJavaのクラスやメソッドを、ローカルにあるかのように実行できる機能です。

とはいえ魔法の機能ではありませんし、他のサーバにあるコードを実行するわけですから制限はあります。

まずはこのあたりから説明していこうかと思います。

SpringRemoteにおけるRMI機能の概要

RMI機能の概要

RMIは、サーバ上でRMIサービスを起動しなければなりません。

このときRMIサーバでは、クライアントに公開するクラスとメソッドを宣言します。

そしてクライアントからRMIのプロトコルでサーバに接続し、公開したメソッドを実行できます。

メソッドの実行結果はクライアントに返されます。

この後の使用サンプルで見ていきますが、プログラム上は、クライアント側ではメソッドを通常通りに呼び出して、結果を受け取れます。

ですので、プログラマーはRMI接続のメソッドを扱っているのか、通常のメソッドを扱っているのかを

意識することなくコードを記述できます。

このように、プログラマーがあまりRMIを意識せずに扱うことができるのは、Spring Remoteのおかげです。

RMI機能における制限

Spring RemoteにおけるRMI機能は、主に以下のような制限があります。

①RMIサーバとクライアント間で、使用するクラスのインターフェースを共有する必要があります。

②呼び出すメソッドの引数は、永続化できるクラスでなければなりません。

③メソッドの実行結果(return値)も、永続化できるクラスでなければなりません。

④メソッド内で引数の値が変更された場合でも、クライアント側の引数オブジェクトは変更されません。

⑤サーバ側で、RMIサービスを立ち上げる必要があります。

サーバとクライアントは両方ともローカルにすることもできますが、通常は別々のPCになるはずです。

つまり、Javaのコードもそれぞれに置かれます。

当然ながら呼び出すクラスの構造を、サーバとクライアントの両方でお互いに知らなければ

呼び出すのは難しいことは想像できると思います。

クラスの構造とは例えば、メソッド名や、メソッドの引数の仕様です。

SpringRemoteでは仕様をインターフェースで共有します。それが、①です。

また、永続化できるオブジェクトでなければリモートに情報を渡すのは難しいです。

その結果が②③となります。

その他:

おそらく、クライアント側でアクセス権(java.policy)ファイルに接続先のホストの設定が必要ではないかと思います。

PCが2台ありませんので実際試せていませんので、すみませんが「おそらく」と書きました。

目標

まず、以下のサンプルの目標(ゴール)を示します。

【RMIサーバ側】

AccountServiceというインターフェースを公開し、insertAccount()というメソッドを公開します。

このメソッドは、引数のnameプロパティをSystem.out.println()するだけの単純な機能です。

【クライアント側】

RMI接続し、insertAccount()メソッドを呼び出します。

使用サンプル

RMIサーバの起動設定(Spring設定ファイルサンプル: rmi_server_applicationContext.xml)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xmlns:aop ="http://www.springframework.org/schema/aop"

xmlns:tx ="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-2.5.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<bean id="accService" class="remotetest.AccountServiceImpl" />

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">

<property name="serviceName" value="AccountService" />

<property name="service" ref="accService"/>

<property name="serviceInterface" value="remotetest.AccountService" />

<property name="registryPort" value="41199" />

</bean>

</beans>

RMIサービスの起動は、RmiServiceExporterクラスを使用します。

Spring上での記述は、上記のとおりです。

ここでは、AccountServiceというインターフェースを登録して、公開しています。

サーバ側の公開インターフェースの実装サンプル

【インターフェースサンプル (remotetest.AccountService.java)】

public interface AccountService {

public void insertAccount(Account acc);

}

【実装サンプル】

public class AccountServiceImpl implements AccountService{

public void insertAccount(Account acc) {

System.out.println("call insertAccount [" + acc.getName() + "]");

accs.add(acc);

}

}

ここでは、引数のnameプロパティを標準出力にprintするだけの機能にしました。

引数に使用するクラスのサンプル(remotetest.Account.java)

public class Account implements Serializable {

private static final long serialVersionUID = 1L;

private String name;

public String getName(){ return name;}

public void setName(String name) {

this.name = name;

}

}

永続化できるようにSerializableをimplementsします。

RMIサーバの起動サンプル (remotetest.RemoteServer.java)

public class RemoteServer {

FileSystemXmlApplicationContext context;

public void start(){

try {

String file = new File(getClass().getResource("rmi_server_applicationContext.xml").toURI()).getAbsolutePath();

context = new FileSystemXmlApplicationContext(file);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

public void end(){

//RMI登録したサービスは明示的に落とす必要があり、

//RmiServiceExporterはdestory()されたときにサービスを落とします。(destroy()しなければRMIは起動しっぱなしです)

this.context.destroy();

}

}

Springの設定ファイルを読み込むだけで、RMIサーバを起動できます。

ここでは、Spring設定ファイルは、このクラスと同じ階層においてあるものとしています。

また、PCを2台用意するのは面倒ですのでこのサンプルでは、1つのPC上でサーバとクライアントの両方の役割を果たします。

以下のサンプルを見ると分かりますが、クライアント側でRMIサーバの起動を行っています。

実際に使用するときは、上記のRemoteServerをサーバ側のmainメソッドで呼び出すようにしてください。

クライアント側Spring設定ファイルサンプル(rmi_client_applicationContext.xml)

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"

xmlns:util="http://www.springframework.org/schema/util"

xmlns:aop ="http://www.springframework.org/schema/aop"

xmlns:tx ="http://www.springframework.org/schema/tx"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/util

http://www.springframework.org/schema/util/spring-util-2.5.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<bean id="remoteAccService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">

<property name="serviceUrl" value="rmi://localhost:41199/AccountService" />

<property name="serviceInterface" value="remotetest.AccountService" />

</bean>

</beans>

RMI接続するには、RmiProxyFactoryBeanクラスを使用します。

Spring設定上では、上記のようにbean定義を記述するだけでよいです。

クライアントとサーバの2つのPCを用意するのは面倒ですので、ここでは、クライアントとサーバを同じlocalhostで起動します。

実際には、serviceUrlは、起動したRMIサーバのURLを記述してください。

また、serviceInterfaceプロパティでは、AccountServiceというinterfaceを設定しています。

これがサーバ側とクライアント側でのインターフェースの共有になります。

クライアント側RMI呼び出しサンプル

public class RemoteClient {

static RemoteServer rs = new RemoteServer();

static Thread t = new Thread(){

@Override

public void run() {

rs.start();

System.out.println("** start rmi server ****");

}

@Override

public void interrupt() {

rs.end();

super.interrupt();

}

};

static public void main(String[] args) throws Exception{

startServer();

//

String file = RemoteClient.class.getResource("rmi_client_applicationContext.xml").getPath();

ApplicationContext context = new FileSystemXmlApplicationContext(file);

//同じインスタンスが返って来る

AccountService as1 = (AccountService)context.getBean("remoteAccService");

Account acc = new Account();

acc.setName("あいう");

as1.insertAccount(acc);

stopServer();

}

static void startServer() throws Exception{

t.start();

Thread.sleep(5*1000);

}

static void stopServer() throws Exception {

t.interrupt();

}

}

クライアント側の準備は、Spring設定ファイルを読み込むだけです。

また、呼び出しはRMIのオブジェクトを指定のインターフェース(ここではAccountService)でキャストして使用するだけです。

通常のSpringの使い方と全く同じです。

ですので、特にRMI通信を使用していることは意識する必要がありません。

将来、Spring設定ファイルを書き変えるだけで、RMIでない方法と入れ替えることも簡単にできることが分かると思います。

【サンプルの動作】

サーバ側では、与えられた引数のnameプロパティを標準出力に記述するだけです。

クライアント側では、nameプロパティに"あいう"と入力していますので、サーバ側で"あいう"と記述してくれます。

起動方法

このサンプルにおける起動方法は、RemoteClientクラスのメインメソッドを起動するだけです。

たったこれだけでRMIを実現できます。

ただ、上でも記述していますが、このサンプルはPC1台の上でRMIサーバとクライアント両方の役割を果たしています。

実際には、他のPC上でRMIサーバを起動しなければなりません。

RemoteServer クラスを起動するmainメソッドを持ったクラスを作成し、サーバ側に持って行きmainメソッドを起動します。

起動が確認できたところで、クライアント側のクラスを起動してRMI接続します。

サーバ側でRMIを起動できないうちは、接続エラーになりますので気をつけてください。

Created Date: 2011/02/01