关于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的入门使用以及我所遇到的相关问题.

 

posted @ 2013-11-09 13:21  sloopf  阅读(376)  评论(0)    收藏  举报