Java远程方法调用,即Java RMI(Java Remote Method Invocation)是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

      RMI的用途:RMI的 用途是为分布式Java应用程序之间的远程通信提供分布式服务。目前主要应用时封装在各个J2EE项目框架中,例如Spring,EJB(Spring和EJB均封装了RMI技术)

         RMI的局限RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议,由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信(意思是只支持客户端和服务器端都是Java程序的代码的远程调用)。

        RMI的使用局限 由于客户机和服务器都是使用Java编写的,二者平台兼容性的要求仅仅是双方都运行在版本兼容的Java虚拟机上。
        RMI调用远程方法的参数和返回值
当调用远程对象上的方法时,客户机除了可以将原始类型的数据作为参数一外,还可以将对象作为参数来传递,与之相对应的是返回值,可以返回原始类型或对象,这些都是通过Java的对象序列化(serialization)技术来实现的。(换而言之:参数或者返回值如果是对象的话必须实现Serializable接口)

        RMI体系结构:

存根/框架(Stub/Skeleton)层:客户端的存根和服务器端的框架;这是RMI应用层与其他部分的接口,它传输给远程引用层的数据是从调度流中提取而来。调度流使用对象序列化机制实现的,通过这个机制,Java对象可以在地址空间进行传递。Stub是客户端的代理,它实现了所有远程对象的接口;Skeleton是服务器端的实体,它包括了一个被具体远程对象所支持的接口。

远程引用(remote reference)层:处理远程引用行为;主要处理低端传输接口,也负责执行专门的远程引用协议,这个协议是独立于客户端的stub和服务端的skeleton之外的。

传送层(transport):连接的建立和管理,以及远程对象的跟踪;实现具体的客户端到服务器端的网络连接。主要执行以下动作:建立远程地址空间的连接;对连接进行管理和监控连接状态;监听新的调用;建立并维护地址空间的远程对象表;建立新调用的连接;定位远程调用的调度程序,并建立与此调度程序的连接。

为了实现位置透明性,RMI 引入了两种特殊类型的对象:存根(stub)和框架(skeleton)

存根是代表远程对象的客户机端对象
–存根具有和远程对象相同的接口或方法列表,但当客户机调用存根方法时,存根通过RMI 基础结构将请求转发到远程对象,实际上由远程对象执行请求。

在服务器端,框架对象处理“远方”的所有细节。
–程序员完全可以象编码本地对象一样来编码远程对象。框架将远程对象从RMI 基础结构分离开来。在远程方法请求期间,RMI 基础结构自动调用框架对象,因此它可以发挥自己的作用。

        RMI的具体应用主要来所可以归纳为三个角色:

服务提供者:实现了服务接口,为其他客户端提供服务;
注册服务器:是一个提供服务注册的实体,服务器提供者需要把他的服务注册到这个才可以被其他客户查找使用;
客户端      :服务的使用者,从注册服务器查找到服务,再使用服务。

构建RMI应用的步骤基本如下:
      服务器端:
•  创建远程接口
•  实现远程接口提供的服务(即方法)
•  启动注册服务器
•  创建和注册服务实例
      客户端:
•  根据注册的服务名查×××得到实例的引用
•  调用实例方法

        实际应用中,我们可以将RMI部署为2层到多层应用。本实例中,我们将RMI部署为3层:客户层─应用层─后台。按照设计模块将RMI部署划分为4个包:Client(客户端),Check(检查,调试),Application(应用层,RMI服务端)和Server(后台服务端,例如:数据库服务器,文件目录服务器等)。

 

 

3)任何时候都不要考虑将过多的“权力”下放给客户端,而是尽可能将业务逻辑地放在应用层。这个问题往往产生在为了方便客户端使用服务端的资源

 例子:

 1. 在Eclipse里面创建一个server 端的project。然后,创建一个接口,这个接口是你要向client端开放的方法定义。它叫做:UserManagerInterface,而且必须继承Remote接口。

 
  
  1. package dataserver.rmi.stub; 
  2.  
  3. import java.rmi.Remote; 
  4. import java.rmi.RemoteException; 
  5.  
  6. import dataserver.rmi.bean.Account; 
  7.  
  8. public interface UserManagerInterface extends Remote{ 
  9.     public String getUserName() throws RemoteException; 
  10.     public Account getAdminAccount() throws RemoteException; 

2. 为了证明RMI中,“面向对象”或者是“无缝传递JAVA Object”是何等简单,我们需要定义一个Account类,该类是一个Bean,必须实现implements Serializable序列化接口。这是一个可以在client和server传输的可序列化对象。

 
  
  1. package dataserver.rmi.bean; 
  2.  
  3. import java.io.Serializable; 
  4.  
  5. public class Account implements Serializable,Cloneable{ 
  6.  
  7.     /** 
  8.      *  
  9.      */ 
  10.     private static final long serialVersionUID = -1858518369668584532L; 
  11.     private String username; 
  12.     private String password; 
  13.      
  14.     public String getUsername() { 
  15.         return username; 
  16.     } 
  17.     public void setUsername(String username) { 
  18.         this.username = username; 
  19.     } 
  20.     public String getPassword() { 
  21.         return password; 
  22.     } 
  23.     public void setPassword(String password) { 
  24.         this.password = password; 
  25.     } 
  26.      

3. 此时,需要实现你已经开放的接口:

 
  
  1. package dataserver.rmi; 
  2.  
  3. import java.rmi.RemoteException; 
  4.  
  5. import dataserver.rmi.bean.Account; 
  6. import dataserver.rmi.stub.UserManagerInterface; 
  7.  
  8. public class UserManagerImpl implements UserManagerInterface { 
  9.  
  10.     public UserManagerImpl() throws RemoteException { 
  11.         //super(); 
  12.         // TODO Auto-generated constructor stub 
  13.         //UnicastRemoteObject.exportObject(this); 
  14.     } 
  15.  
  16.     /** 
  17.      *  
  18.      */ 
  19.     private static final long serialVersionUID = -3111492742628447261L; 
  20.  
  21.     public String getUserName() throws RemoteException { 
  22.         // TODO Auto-generated method stub 
  23.         return "Tommy Lee"
  24.     } 
  25.  
  26.     public Account getAdminAccount() throws RemoteException { 
  27.         // TODO Auto-generated method stub 
  28.         Account account=new Account(); 
  29.         account.setUsername("admin"); 
  30.         account.setPassword("admin"); 
  31.         return account; 
  32.     } 
  33.  

4. 定义一个主程序入口,注册你已经实现的RMI接口,包括开放端口等。其实很简单:

把我们的接口名称,命名为“userManager”,方便client进行调用

 
  
  1. package dataserver.entry; 
  2.  
  3. import java.rmi.AlreadyBoundException; 
  4. import java.rmi.RemoteException; 
  5. import java.rmi.registry.LocateRegistry; 
  6. import java.rmi.registry.Registry; 
  7. import java.rmi.server.UnicastRemoteObject; 
  8.  
  9. import dataserver.rmi.UserManagerImpl; 
  10. import dataserver.rmi.stub.UserManagerInterface; 
  11.  
  12. public class Entry { 
  13.  
  14.     public static void main(String []args) throws AlreadyBoundException, RemoteException{ 
  15.      
  16.  
  17.             UserManagerImpl userManager=new UserManagerImpl(); 
  18.             UserManagerInterface userManagerI=(UserManagerInterface)UnicastRemoteObject.exportObject(userManager,0); 
  19.             // Bind the remote object's stub in the registry 
  20.             Registry registry = LocateRegistry.createRegistry(2001); 
  21.             registry.rebind("userManager", userManagerI); 
  22.             System.out.println("server is ready"); 
  23.     } 

 

5. Server端的代码已经全部写完,但是还要把bean类(Account)和接口类(UserMangerInterface)打包成jar,以便可以在下面导入进client端的项目中。

项目--》右键--》导出--》jar--》选择bean和interface--》命名为RmiServerInterface.jar--》finish

6.  开始创建client端的程序。新建一个project。创建完成后,把刚才jar包导入进client的项目中。

7.  导入我们的接口jar以后,可以开始编写一个client端的主程序,并调用server端的方法。

 
  
  1. package weiblog.rmi; 
  2. import java.rmi.NotBoundException; 
  3. import java.rmi.RemoteException; 
  4. import java.rmi.registry.LocateRegistry; 
  5. import java.rmi.registry.Registry; 
  6.  
  7. import dataserver.rmi.stub.UserManagerInterface; 
  8.  
  9. public class Entry2 { 
  10.  
  11.     public static void main(String []args){ 
  12.          
  13.         try { 
  14.             Registry registry = LocateRegistry.getRegistry("localhost",2001); 
  15.             UserManagerInterface userManager = (UserManagerInterface) registry.lookup("userManager"); 
  16.             System.out.println(""+userManager.getAdminAccount().getUsername() 
  17.                     +userManager.getAdminAccount().getPassword()); 
  18.         } catch (RemoteException e) { 
  19.             // TODO Auto-generated catch block 
  20.             e.printStackTrace(); 
  21.         } catch (NotBoundException e) { 
  22.             // TODO Auto-generated catch block 
  23.             e.printStackTrace(); 
  24.         } 
  25.          
  26.     } 

8. 启动server端的主程序,然后启动client端的主程序。

server控制台打印:server is ready

client控制台打印:adminadmin

 

 对于上述框架,请注意以下3个容易忽视的点:

1)客户端和服务器必须同步共享远程接口定义和存根,特别是客户端和服务器端在不同主机的情形下,必须将更新后的远程接口定义和存根同时分发到客户端和服务器端

(2)当客户端和服务器端在不同主机时要考虑部署策略文件(安全因素)。