关于Java RMI的初步理解
RMI概述
RMI,远程方法调用,用来使客户机上运行的程序可以调用远程服务器上的对象。关于RMI的实现原理入下图所示
。
下面主要讲述如何编写一个简单的RMI程序
一个完整的步骤
1.定义一个远程接口
该接口为客户端与服务器端共享的接口,必须继承Remote接口,而且每个方法都要抛出RemoteException异常,因为远程方法调用总是存在失败的可能
1 import java.rmi.Remote; 2 import java.rmi.RemoteException; 3 4 public interface Service extends Remote{ 5 public void doSomething()throws RemoteException; 6 }
2.实现该远程接口
实现类需要继承UnicastRemoteObject,这个类的构造器使得实现类具有远程访问的功能
1 import java.rmi.server.UnicastRemoteObject; 2 import java.rmi.RemoteException; 3 4 public class ServiceImpl extends UnicastRemoteObject implements Service{ 5 public ServiceImpl()throws RemoteException{ 6 super(); 7 } 8 9 @Override 10 public void doSomething()throws RemoteException{ 11 System.out.println("I am doing something..."); 12 } 13 }
3.实现服务器端代码
我们要做的就是将这个远程对象注册到RMI注册表中去,即给远程对象一个名字,好让客户端通过这个名字找到这个远程对象。这里忽略了异常处理
1 import java.rmi.Naming; 2 3 public class Server{ 4 public static void main(String[] args)throws Exception{ 5 Service service=new ServiceImpl(); 6 Naming.rebind("rmi://localhost/service",service); 7 } 8 }
4.实现客户端代码
客户端要做的有两件事,一是在RMI注册表查找并获取远程对象,第二个就是调用远程对象是的方法。这里忽略了异常处理
1 import java.rmi.Naming; 2 3 public class Client{ 4 public static void main(String... args) throws Exception{ 5 Service service=(Service)Naming.lookup("rmi://localhost/service"); 6 service.doSomething(); 7 } 8 }
至此,代码编写工作已经完成,接下来就是部署RMI应用程序
将所有代码编译,为了生成Stub,还需键入命令
$ rmic ServiceImpl
有兴趣的话,在rmic后加入-keep参数,可以看到生成代理类的源码
为了模拟远程调用,创建两个文件夹,分别存放服务器端代码和客户端代码,分别如下所示
+-server +-client
| +-Server.class | +-Client.class
| +-Service.class | +-Service.class
| +-ServiceImpl.class | +-ServiceImpl_Stub.class
| +-ServiceImpl_Stub.class
现在运行RMI程序
1.首先启动注册服务程序
2.启动服务器
3.执行客户端
运行结果如图所示

以上为一个完整的RMI程序创建流程,接下来处理一些细节问题:
1.运行程序前都要运行rmiregistry运行注册服务程序,那么这个步骤是否可以省略?
JDK提供了自举注册服务,服务器程序可以自举注册远程对象。LocateRegistry 类用于获得对特定主机(包括本地主机)上引导远程对象注册表的引用,或用于创建一个接受对特定端口调用的远程对象注册表。稍微改动一下服务器端代码,如下所示,就可以省去注册服务程序
1 import java.rmi.registry.LocateRegistry; 2 import java.rmi.Naming; 3 4 public class LocateServer{ 5 public static void main(String... args)throws Exception{ 6 LocateRegistry.createRegistry(1099); 7 Service service=new ServiceImpl(); 8 Naming.bind("rmi://localhost/service",service); 9 } 10 }
2.在使用rmic时只生成了Stub,为什么没有生成Skeleton?
其实连Stub都不用生成。Java 1.2利用反射API直接将客户调用分派给远程服务。到了Java 5,RMI和动态代理搭配使用,动态代理会自动生成Stub。所以不再需要rmic,这一切都在幕后被处理掉了。不信可以试一下。
3.关于UnicastRemoteObject,有时候不想让自己的实现类继承该类,或自己的实现类要继承其它类
Java也提供了相应的解决办法。这种情况下,就需要我们手动初始化远程对象,利用UnicastRemoteObject的exportObject方法。这里给出一个稍作修改的ServiceImpl示例
1 import java.rmi.RemoteException; 2 import java.rmi.server.UnicastRemoteObject; 3 4 public class ServiceImpl implements Service{ 5 public ServiceImpl() throws RemoteException{ 6 UnicastRemoteObject.exportObject(this); 7 } 8 9 @Override 10 public void doSomething() throws RemoteException{ 11 System.out.println("I am doing something..."); 12 } 13 }
但是这样是不能直接运行的,Java会给出一个ClassNotFoundException,原因是ServiceImpl_Stub未找到。似乎Java在幕后只能对UnicastRemoteObject的子类下黑手,其它类无法识别。这样,解决办法也很简单,就是自己运行rmic生成Stub。
4.远程接口中函数的方法签名与返回值必须实现Serializable接口,因为RMI底层是通过Java的序列化机制来传输对象
另一种实现:使用JNDI
Service接口与ServiceImpl不需要做任何改动,主要是服务器端代码和客户端代码的实现
1.实现服务器端代码
1 import java.util.Properties; 2 import java.rmi.registry.LocateRegistry; 3 import javax.naming.Context; 4 import javax.naming.InitialContext; 5 6 public class JNDIServer{ 7 public static void main(String... args) throws Exception{ 8 LocateRegistry.createRegistry(1099); 9 10 Properties props=new Properties(); 11 props.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory"); 12 props.setProperty(Context.PROVIDER_URL,"rmi://localhost:1099"); 13 Context context=new InitialContext(props); 14 15 Service service=new ServiceImpl(); 16 context.bind("service",service); 17 18 context.close(); 19 } 20 }
2.实现客户端代码
1 import javax.naming.Context; 2 import javax.naming.InitialContext; 3 import java.util.Properties; 4 5 6 public class JNDIClient{ 7 public static void main(String... args) throws Exception{ 8 Properties props=new Properties(); 9 props.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory"); 10 props.setProperty(Context.PROVIDER_URL,"rmi://localhost:1099"); 11 Context context=new InitialContext(props); 12 13 Service service=(Service)context.lookup("service"); 14 service.doSomething(); 15 } 16 }
编译运行程序,结果如图所示

上述代码可以简化:
简化后的服务器端代码如下
1 import java.rmi.registry.LocateRegistry; 2 import javax.naming.Context; 3 import javax.naming.InitialContext; 4 5 public class JNDIServer{ 6 public static void main(String... args) throws Exception{ 7 LocateRegistry.createRegistry(1099); 8 9 Service service=new ServiceImpl(); 10 Context context=new InitialContext(); 11 context.bind("rmi://localhost:1099/service",service); 12 13 context.close(); 14 } 15 }
简化后的客户端代码如下
1 import javax.naming.Context; 2 import javax.naming.InitialContext; 3 4 public class JNDIClient{ 5 public static void main(String... args) throws Exception{ 6 Context context=new InitialContext(); 7 8 Service service=(Service)context.lookup("rmi://localhost/service"); 9 service.doSomething(); 10 } 11 }
这里就不再给出运行结果
以上仅给出Java RMI的入门使用以及我所遇到的相关问题.

浙公网安备 33010602011771号