翻译原文:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/hello/hello-world.html

简单入门教你用java远程调用(java RMI)一步一步创建一个分布式的经典hello world程序。当你学习这个例子的时候,可能会产生一些相关的问题,你可以java RMI FAQ和 archives of theRMI-USERS mailing list中寻找答案。

分布式的Hello world示例使用一个简单的客户端调用一个服务器上的远程方法,此服务可能在另一个远程主机上,客户端从该主机上获取Hello World消息。

此教程有以下几步:

1)  定义一个远程接口

2)  实现服务

3)  实现客户端

4)  编译原源文件

5)  Java RMI注册,启动服务端和客户端

此教程包含的文件说明:

1)  Hello.java—一个远程接口

2)  Server.java—一个实现了远程接口的远程对象

3)  Client.java—一个客户端,调用远程接口的方法

定义远程接口:

远程对象是实现了远程接口的类的一个实例。远程接口继承接口:java.rmi.Remote,并声明一组远程方法,每一个远程方法都必须声明抛出异常:java.rmi.RemoteException(或抛出RemoteException的父类),这是javaRMI特定的异常处理类。

下面是远程接口定义的例子,example.hello.Hello。此接口只包含一个方法:sayHello,并返回一个字符串给调用者。代码如下:

package example.hello;
 
import java.rmi.Remote;
import java.rmi.RemoteException;
 
public interface Hello extends Remote {
    String sayHello() throws RemoteException;
}

远程方法调用与本地方法调用相比,可能出现更多的失败可能(如通信问题,服务端问题等等),通过RemoteException可以将失败原因反馈给我们。

实现服务端

在本文中,一个“server”类是一个包含main方法,并在main方法中实例化了远程接口的实现类。导入远程对象,然后将此实例绑定给java RMI注册表的一个名词。包含这样一个main方法的类,可以是实现类自身,也可以是另一个类。

在此示例中,服务端的main方法定义在Server类中,Server类也实现了远程接口Hello,服务端的main方法实现下面两个功能:

1)  创建导入远程对象

2)  将远程对象注册到javaRMI注册表

下面是Server类的源码:

package example.hello;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class Server implements Hello {
	
	public Server(){}

	public String sayHello(){
		return "Hello, world!";
	}
	
	public static void main(String[] args){
		try{
			Server object = new Server();
			//导入远程对象
			Hello stub = (Hello)UnicastRemoteObject.exportObject(object, 0);
			//绑定远程对象stub到注册表
			Registry registry = LocateRegistry.getRegistry();
			registry.bind("Hello", stub);
			System.out.println("Server ready..");
		}catch(Exception e){
			System.out.println("Server Exception:"+e.toString());
			e.printStackTrace();
		}
	}
}

上面的代码,Server类实现了远程接口Hello和远程方法sayHello。sayHello方法并不需要声明抛出任何异常,因为方法实现自身并不会抛出RemoteException或其他checked exceptions。

注意:一个类可以自定义方法,这些方法可以不用包含在远程接口内。但这些方法只能在本地虚拟机调用,而不能被远程虚拟机调用。

创建和导入远程对象

服务端的main方法需要创建一个远程对象来提供服务。此外远程对象必须在java RMI运行时导入,以便接受远程调用。这些步骤由下面两句话完成:

Server object = new Server();
Hello stub = (Hello)UnicastRemoteObject.exportObject(object, 0);

静态方法UnicastRemoteObject.exportObject 的作用:当获得任意TCP端口的RMI请求,就导出此远程对象,并将此实例传递到客户端。调用exportObject方法后,程序运行时会监听新的server socket或者用共享server socket来获取远端调用。返回的stub与远程对象一样,实现相同系列的接口,并包含了远程对象可以接触的主机名和端口。

注意:对于J2SE 5.0版本,远程对象的stub类不再需要rmic stub编译器预生成,除非远程对象需要运行在5.0以前的版本上。如果你的应用需要支持这样的客户端,你将需要为远程对象生成一个stub类,以供应用和下载。细节有参考文档,参考原文。

注册远程对像到java RMI注册表

调用者(client、peer或applet)调用远程对象的方法,调用这首先要获得远程对象的一个引用。Java RMI为应用提供一个registry API,来绑定一个名称和一个远程对象引用,这样,客户端通过远程对象的名称来查找获取对应的引用。

Java RMI registry 是一个简单的命名服务,客户端可以通过此注册表找到名称对应的远程对象引用(一个stub)。总体而言,一个注册表被使用,发生在一个客户端查找第一个远程对象时。典型的,第一个对象会提供找到其他对象的特定于应用程序的支持。例如,引用的获取,可以通过参数,返回值,或另一个远程方法调用的形式获取。

一旦远程对象注册到服务端的registry,调用者可以通过名称查询对象,获取远程对象引用,进一步调用远程方法。

下面的代码为本机的注册表在默认注册端口获取一个注册表实例,并将stub引用注册绑定到名称“Hello”上,完成注册:

Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);

静态方法LocateRegistry.getRegistry()没有参数,返回一个registry,此registry实现了远程接口java.rmi.registry.Registry。Registry发送调用本机注册表,默认端口是1099,调用registry.bind()方法,以绑定名称和引用。

主要:LocateRegistry.getRegistry()调用会返回一个恰当的registry的引用。此方法不会检查是否已经有一个registry在运行了。如果没有registry运行在本机的1099 TCP端口,此时调动registry.bind()方法会抛出RemoteException异常。

实现客户端

客户端程序获取服务主机注册表中的一个引用,此引用可以通过名称查找,最后通过引用调用sayHello方法。

客户端源码:

package example.hello;
 
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
public class Client {
 
    public Client(){}
   
    public static void main(String[] args){
       String host = (args.length<1)?null:args[0];
       try{
           Registry registry = LocateRegistry.getRegistry(host);
           Hellostub = (Hello)registry.lookup("Hello");
           String response = stub.sayHello();
           System.out.println("response:"+response);
       }catch(Exception e){
           System.out.println("ClientException:"+e.toString());
           e.printStackTrace();
       }
    }
}

客户端通过调用静态方法LocateRegistry.getRegistry()方法获取注册表的实例,调用时要指定主机名。如果不指定主机名,传入null参数将被默认指定为本机。

然后,客户端从服端的注册表中获取远程对象的引用,继而调用远程方法。

最后,通过远程对象的引用调用sayHello方法,过程如下:

1)  客户端运行时,会根据远程对象引用中的主机和端口信息,打开一个到服务端的连接,然后序列化调用数据;

2)  服务端运行时,接收到调用请求,将请求转发到远程对象,将执行结果(返回字符串“Hello, world!”)序列化后,返回客户端

3)  客户端运行时,收到回执,反序列化,将结构返回调用者。

4)  调用者打印返回消息。

编译源文件

本例中源文件编译命令如下:

 Javac–d destDir Hello.java Server.java Client.java

destDir是一个自定文件夹,包含上面的三个文件。

启动java RMI registry,server和client

启动java RMI registry

启动注册表,在服务器主机运行rmi registry命令,名称运行不会产生任何输出(即便启动成功),这是典型的后台程序。要了解更多,参看rmi registry工具文档(见原文此处链接)。

例如,在Solaris™ Operating System:

rmiregistry &

或在,在windows平台上:

Start rmiregistry

默认的,注册表使用启动端口1099。如果要指定启动端口,可以使用下面的命令(在windows平台2001端口打开注册表):

start rmiregistry 2001

如果registry运行在1099以外的端口上,你就需求在Server和Cliet代码中,调用LocateRegistry.getRegistry()方法时,指定端口号。如,若指定端口为2001,则调用代码应该改为:

Registry registry = LocateRegistry.getRegistry(2001);

启动服务端,执行下面命令:

操作系统Solaris Operating System:

  Java–classpath classDir –Djava.rmi.server.codebase=file:classDir/example.hello.Server &

操作系统Windows:

  Startjava –classpath classDir –Djava.rmi.server.codebase=file:classDir/example.hell.Server

classDir是三个class文件的根路径,设置java.rmi.server.codebase系统属性确保注册表可以加载远程接口定义(注意斜线)。关于此属性的更多信息,见原文此处链接。

服务端的输出如下:

         Serverready

服务端将保持运行状态,除非用户终止它。

启动客户端

一旦服务端启动,客户端可以用一下命令运行:

         Java–classpath classDir example.hello.Client

classDir是class文件的根路径,输出如下:

         response:Hello,world!

 

另一个RMI Hello World参考例子(转):http://lavasoft.blog.51cto.com/62575/91679/


个人小结:java RMI工作原理,第一步,服务端要定义一个远程对象,并将对象注册到本地的注册表中,并为此对象绑定一个名称。(第一步为客户端调用远程对象提供物质基础)第二步,客户端要知道服务端的主机名和端口,以及远程对象的名称,当发起请求时,客户端RMI底层会自动根据host和port建立连接,然后获取服务端的注册表,通过对象名称在此注册表中查询远程对象,如果查询成功,则调用实例中的方法,将执行结果实例化后返回客户端。