kapok

垃圾桶,嘿嘿,我藏的这么深你们还能找到啊,真牛!

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  455 随笔 :: 0 文章 :: 76 评论 :: 0 Trackbacks

http://xs.blogchina.com/article_193148.1248322.html

 

Java 远程方法调用(RMI)

 1. RMI 简介 
 2. RMI 体系结构
 3. 远程异常
 4. 服务器开发步骤概述
 5. 编写远程接口
 6. 实现远程接口
 7. 编写 RMI 服务器概述
 8. 设置安全性管理器
 9. 命名远程对象
 10. 生成存根和框架
 11. 客户机开发概述
 12. 应用程序 vs. applet
 13. 编写 RMI 客户机应用程序
 14. 设置安全性管理器
 15. 查找对象
 16. 调用远程方法
 17. 编写 RMI 客户机 applet
 18. 编写用于 applet HTML 页面
 19. 部署概述
 20. 启动 RMI 注册表
 21. 启动对象服务器
 22. 运行客户机应用程序
 23. 运行客户机 applet

 

RMI 简介
第 1 页(共23 页)
我们将从 Java 远程方法调用(RMI)开始讨论,Java 1.1 中引入了这种技术。
RMI 的用途是使分布在不同虚拟机中的对象的外表和行为都象本地对象一样。调用远程对象的虚拟机有时称为客户机。类似地,我们将包含远程对象的虚拟机称为服务器。
获取远程对象的引用和获取本地对象的引用有点不同,但一旦获得了引用,就可以象调用本地对象一样调用远程对象,如下面的代码片段所示。RMI 基础结构将自动截取请求,找到远程对象,并远程地分派请求。
这种位置透明性甚至包括垃圾收集。也就是说,客户机不必特地释放远程对象,RMI 基础结构和远程虚拟机为您处理垃圾收集。

RMI 体系结构
第 2 页(共23 页)
为了实现位置透明性,RMI 引入了两种特殊类型的对象:存根(stub框架(skeleton
存根是代表远程对象的客户机端对象。存根具有和远程对象相同的接口或方法列表,但当客户机调用存根方法时,存根通过 RMI 基础结构将请求转发到远程对象,实际上由远程对象执行请求。
在服务器端,框架对象处理"远方"的所有细节,因此实际的远程对象不必担心这些细节。也就是说,您完全可以象编码本地对象一样来编码远程对象。框架将远程对象从 RMI 基础结构分离开来。在远程方法请求期间,RMI 基础结构自动调用框架对象,因此它可以发挥自己的作用。
关于这种设置的最大的好处是,您不必亲自为存根和框架编写代码。JDK 包含工具 rmic,它会为您创建存根和框架的类文件。

远程异常
第 3 页(共23 页)
实际上,RMI 使用 TCP/IP 套接字来传达远程方法请求。尽管套接字是相当可靠的传输,但还是有许多事情可能出错。例如,假设服务器计算机在方法请求期间崩溃了。或者,假设客户机和服务器计算机是通过因特网连接的,而客户机掉线了。
关键在于,使用远程对象时比使用本地对象时有更多可能出错的机会。因此客户机程序能够完美地从错误中恢复就很重要了。
因此要让客户机知道此类错误,每个将要被远程调用的方法都必须抛出 RemoteException,java.rmi 包中定义了该异常。

服务器开发步骤概述
第 4 页(共23 页)
让我们研究一下编写对象服务器所涉及的步骤。我们将花一些时间研究客户机端。
您需要做的第一件事情是定义用于远程对象的接口。这个接口定义了客户机能够远程地调用的方法。远程接口和本地接口的主要差异在于,远程方法必须能抛出上一页描述的 RemoteException。
接下来,编写一个实现该接口的类。
然后,编写在服务器上运行的主程序。这个程序必须实例化一个或多个服务器对象,然后,通常将远程对象注册到 RMI 名称注册表,这样客户机就能够找到对象。
最后,生成存根和框架的代码。JDK 提供了 rmic 工具,它读取远程对象的类文件并为存根和框架创建类文件。
编写远程接口
第 5 页(共23 页)
下面的代码显示了一个简单远程接口的接口定义。实现这个接口的对象提供三个方法:一个方法返回字符串、一个方法接受字符串作为参数、而另一个方法不接受参数也不返回任何结果。正如先前提到的,这些方法必须能抛出 RemoteException,如果客户机和服务器之间的通信出错,则客户机将捕获此异常。
注:该接口本身继承了 java.rmi 包中定义的 Remote 接口。Remote 接口本身没有定义方法,但通过继承它,我们说明该接口可以被远程地调用。
这里是编写远程接口的代码:
import java.rmi.*;
public interface Meeting extends Remote
{
  public String getDate ()
     throws RemoteException;
  public void setDate ( String date )
     throws RemoteException;
  public void scheduleIt()
     throws RemoteException;
}
实现远程接口
第 6 页(共23 页)
现在,让我们研究一个实现远程 Meeting 接口的类。通常,MeetingServer 继承 UnicastRemoteObject 类,UnicastRemoteObject 类提供远程对象所需的基本行为。术语"单播(unicast)"是指每个客户机存根引用单个远程对象的现象。以后,RMI 可能会允许"多播(multicasting)",即一个存根可以引用几个对等的远程对象。使用多播,RMI 基础结构可以均衡一组远程对象之间的负载。
下面的代码样本显示了两个方法的实现:Meeting 接口中定义的 getDate 方法和一个无参数构造器。请注意:这两者都抛出 RemoteException;所有由客户机远程调用的方法和构造器都需要抛出这个异常。在本示例中,构造器无实际工作可做,但我们还是需要定义它,这样它能够抛出远程异常。
但是 getDate 方法很有趣。它向调用者返回一个字符串的引用。虽然这看起来可能很简单,但是,在这里 RMI 基础结构和框架以及存根实际上有很多工作要做。它们必须协同工作,以便将字符串的一个副本传回客户机,然后,在客户机的虚拟机中作为对象重新创建。
这里是实现远程接口的代码:
import java.rmi.*;
import java.rmi.server.*;
public class MeetingServer
  extends UnicastRemoteObject
  implements Meeting
{
  private String ivDate = new String ( "1/1/2000" );
  public MeetingServer()
    throws RemoteException
  {
  }
 
  public String getDate()
    throws RemoteException
  {
    return ivDate;
  }
...
}
编写 RMI 服务器概述
第 7 页(共23 页)
除了实现接口之外,我们还需要编写服务器的主程序。目前 RMI 不支持作为 applet 的服务器程序,所以主程序必需是独立的 Java 应用程序。您要么为主程序编码一个单独的 Java 类,要么象我们在这里所做的一样,只在实现类中编码一个 main 方法。
还请注意,我们编码的 main 函数可以向命令行抛出任何与 RMI 相关的异常。对于象本文中的小样本程序,这样做没有问题,但在实际程序中,您可能要将执行的步骤包括到独立的 try-catch 块中,从而能够更好地执行错误处理。
这里是编写 RMI 服务器的代码结构:
import java.rmi.*;
import java.rmi.server.*;
public class MeetingServer
  extends UnicastRemoteObject
  implements Meeting
{
  ...
  public static void main (String [] args ) throws
    RemoteException, java.net.MalformedURLException,
    RMISecurityException
  {
    // 1. Set Security manager
    // 2. Create an object instance
    // 3. Register object into the name space
  }
}
*** 本图将被删除。留在这里仅供对照/检查用 ***
服务器 main 通常要做的步骤是:
  1. 安装安全性管理器类,它允许服务器程序从其它机器接收存根类
  2. 创建服务器对象的实例
  3. 将服务器对象注册到 RMI 命名注册表以便客户机程序能找到该服务器对象
现在,让我们进一步研究这些步骤。
设置安全性管理器
第 8 页(共23 页)
第一步是安装 RMI 安全性管理器。尽管这不是严格必须的,但它确实允许服务器虚拟机下载类文件。例如,假设客户机调用服务器中的方法,该方法接受对应用程序定义的对象类型(例如 BankAccount)的引用。通过设置安全性管理器,我们允许 RMI 运行时动态地将 BankAccount 类文件复制到服务器,从而简化了服务器上的配置。
让 RMI 动态地下载这些类的弊端是有安全性风险。也就是说,实质上我们是在让服务器执行来自另一台机器的代码。虽然我们希望这些类文件不会危及服务器,但如果希望避免这样的风险,则您的 RMI 服务器不应该安装安全性管理器。然后,您必须确保将所有类文件安装在本地服务器的类路径中。
这里是用于设置安全性管理器的代码:
import java.rmi.*;
import java.rmi.server.*;
public static void main (String [] args ) throws
    RemoteException, java.net.MalformedURLException,
    RMISecurityException
{
    System.setSecurityManager (
        new RMISecuritymanager() );
        
    MeetingServer ms = new MeetingServer();
   
    Naming.rebind (
      "rmi://myhost.com/Meeting", ms );
}
*** 本图将被删除。留在这里仅供对照/检查用 ***
注:传递对象参数类型是涉及许多方面的主题,因为有两种方法实现它。一种是在通信线路上仅传递引用;另一种是将对象序列化并在远程创建新对象。在本教程中,我们不会更深入地讨论这些问题,但您应该阅读 JDK 中的 RMI 文档以获取更多详细信息。
命名远程对象
第 9 页(共23 页)
服务器的下一步工作是创建服务器对象的初始实例,然后将对象的名称写到 RMI 命名注册表。RMI 命名注册表允许您将 URL 名称分配给对象以便客户机查找它们。要注册名称,需调用静态 rebind 方法,它是在 Naming 类上定义的。这个方法接受对象的 URL 名称以及对象引用。
名称字符串是很有趣的部分。它包含 rmi:// 前缀、运行 RMI 对象的服务器的计算机主机名和对象本身的名称,这个名称正是您所想要的。注:您可以调用由 java.net.InetAddress 类定义的 getLocalHost 方法,而不必象我们在这里所做的一样硬编码主机名。
这里是命名远程对象的代码:
import java.rmi.*;
import java.rmi.server.*;
public static void main (String [] args ) throws
    RemoteException, java.net.MalformedURLException,
    RMISecurityException
{
    System.setSecurityManager (
        new RMISecuritymanager() );
       
    MeetingServer ms = new MeetingServer();
   
    Naming.rebind (
      "rmi://myhost.com/Meeting", ms );
生成存根和框架
第 10 页(共23 页)
编写并编译了服务器实现之后,就准备创建存根和框架类。那很容易:只要运行 JDK 的 rmic 命令,指定实现类文件名(不带扩展名)。rmic 工具将为每个类文件创建一个存根和一个框架。然后,您需要正确地部署这些文件,在讨论完编写客户机端代码之后,我们将讨论部署问题。

客户机开发概述
第 11 页(共23 页)
现在,让我们研究客户机端。首先,您必须确定是想编写客户机独立应用程序还是客户机 applet。应用程序的设置简单些,但 applet 更容易部署,因为 Java RMI 基础结构能够将它们下载到客户机机器。我们将讨论如何实现这两者。
在客户机中,代码需要首先使用 RMI 注册表来查找远程对象。一旦这样做了之后,客户机就可以调用由远程接口定义的方法。
应用程序 vs. applet
第 12 页(共23 页)
让我们来简略看一下 Java 应用程序和 applet 之间的差异。如果编写应用程序,必须定义一个 main 入口点,可以从中执行 RMI 启动代码。然后,您必须在客户机机器上安装应用程序的类文件。如果有多台客户机计算机,则您必须在每台机器上手工安装这个应用程序的类文件。并且,正如我们过一会儿将要看到的,您还要在客户机应用程序计算机上安装一些与 RMI 相关的服务器文件。
相反,如果您决定编写一个 applet,则不需要 main 入口点。applet 重写了由浏览器调用的 init 方法。可以在 init 中编写与您在应用程序的 main 中编写的同类代码。applet 的主要优点是,除了浏览器,您不必在客户机计算机上预安装任何东西 ― Java RMI 基础结构将自动下载所有必要的类文件。但是,请注意,您必须编写一个浏览器能够装入的 HTML 文件。我们将简略地讨论这一切。
编写 RMI 客户机应用程序
第 13 页(共23 页)
让我们研究一下编写 RMI 独立应用程序所包含的步骤。我们照常将 main 方法编码为入口点,并且在其中执行 RMI 初始化步骤。首先设置安全性管理器,以便 RMI 运行时能够下载类文件,然后通过使用 RMI 命名注册表获取一个对远程对象的引用。最后我们就能够调用远程方法了。
这里是编写 RMI 客户机应用程序的代码:
import java.rmi.*;
public class MeetingClient
{
  public static void main ( String [] args )
    throws RemoteException,
           java.net.MalformedURLException,
           java.rmi.NotBoundException
  {
    // 1. Set Security Manager
    // 2. Look up remote object from name space
    // 3. Call remote methods
  }
}
*** 本图将被删除。留在这里仅供对照/检查用 ***
虽然这个代码显示了仅在 main 方法中调用远程方法,但一旦应用程序检索到对象引用,则应用程序也可以在其它方法中调用远程方法。此外,请注意,不需要进行清理,即使对远程对象,Java RMI 基础结构也会保证进行垃圾收集工作。
现在我们将更详细地讨论这些步骤。
设置安全性管理器
第 14 页(共23 页)
该代码看起来和对象服务器 main 中的代码类似。象对象服务器一样,客户机应用程序也可以选择是否设置安全性管理器。并且原因也相似:RMI 运行时会自动将远程对象的存根类文件下载到客户机,但仅当应用程序安装了安全性管理器时才能这样做。如果应用程序使用缺省的安全性管理器,则需要在客户机计算机的类路径预安装存根类文件,否则应用程序将捕获到一个安全性异常。
还应该注意,在这个代码中 main 方法只是将与 RMI 相关的异常抛回到命令行。更健壮的应用程序应该包含 try-catch 块以便本地进行错误处理。
这里是设置安全性管理器的代码:
import java.rmi.*;
  public static void main ( String [] args )
    throws RemoteException,
           java.net.MalformedURLException,
           java.rmi.NotBoundException
  {
    System.setSecurityManager(new RMISecurityManager());
    ...
   
  }

查找对象
第 15 页(共23 页)
一旦应用程序安装了可使用的安全性管理器,它就可以从 RMI 命名注册表检索远程对象的引用。要这样做,需调用静态 lookup 方法,传递对象服务器为远程对象所注册的相同名称。lookup 方法将检索对远程对象的引用并创建存根对象,这里,存根对象存储在变量 r 中。
如果客户机提供的名称与注册表中的名称不匹配,则 lookup 方法抛出 NotBoundException。如果所提供的 URL 无效,则 lookup 抛出 MalformedURLException。在下面的这个简单的代码片段中,没有显式地捕获这些异常,但在实际程序中您需要这样做。
请注意返回的引用类型是 Remote 类型,它是所有远程对象的超类。但是实际上,我们想要的是作为远程接口中定义的对 Meeting 接口的引用。因此,我们需要如下一页所述那样对引用进行向下强制类型转换。
这里是查找对象的代码:
import java.rmi.*;
  public static void main ( String [] args )
    throws RemoteException,
           java.net.MalformedURLException,
           java.rmi.NotBoundException
  {
    System.setSecurityManager(new RMISecurityManager());
    Remote r = Naming.lookup {
        "rmi://myhost.com/Meeting" );
    ...
  }
调用远程方法
第 16 页(共23 页)
在应用程序能够调用远程方法之前,它必须转换远程引用的类型以匹配接口定义,在本示例中是 HelloInterface。虽然您可以使用简单的强制类型转换做到这一点,但这里显示的代码首先通过调用 instanceof 运算符来检查强制类型转换是否有效。当远程对象不是预期的类型时,这种技术可使您可以避免 InvalidCast 运行时异常。
一旦我们成功地对引用进行了强制类型转换,就可以调用远程方法;这里是 getDate 方法,它返回一个字符串。
这里是调用远程方法的代码:
import java.rmi.*;
  public static void main ( String [] args )
    throws RemoteException,
           java.net.MalformedURLException,
           java.rmi.NotBoundException
  {
    ...
    Remote r = Naming.lookup (
        "rmi://myhost.com/Meeting");
   
    String s = null;
   
    if (r instanceof Meeting )
    {
      ms = (Meeting)r;
      s = ms.getDate();
    }
  }
编写 RMI 客户机 applet
第 17 页(共23 页)
现在,让我们研究将 RMI 客户机编写为 applet 而不是独立应用程序时这两者的差异。有三个基本差异:
  1. 通常的 applet 是在 init 方法而不是在 main 编码 RMI 初始化。
  2. 因为您不能更改 init 的方法说明(正在从 Applet 重写它),所以不能将 init 编码为可抛出的异常。因此必须用 try-catch 块处理异常。
  3. 不必安装 RMI 安全性管理器,因为 applet 自动使用允许下载远程类的 AppletSecurityManager。
尽管有这些不同,applet 客户机中与 RMI 相关的代码和应用程序中的基本相同。在这里仍然要使用 RMI 命名注册表来查找远程引用,并将返回的引用强制转换成正确类型等。
applet 和应用程序之间的另一个差异是:要在浏览器内使用 applet,必需编写引用 applet 的 HTML 页面。在下一页我们研究这个问题。
这里是编写 RMI 客户机 applet 的代码:
import java.rmi.*;
public class MeetingClientApplet extends java.applet.Applet
{
  public void init ()
  {
    try
    (
      //1. Look up remote object in the RMI registry
      //2. Call remote methods (can also call from
      //   other methods if you save th reference)
    }
    catch ( Exception e )
    {
    }
  }
}
编写用于 applet HTML 页面
第 18 页(共23 页)
这是一个简单的 HTML 文件,它装入上一页显示的 applet。HTML 页面中关键的一行是 applet 标记,在这个标记中指定 applet 的类文件(无文件扩展名)。然后,需要在 Web 服务器上安装该 HTML 文件和 applet 类文件。当用户在其支持 Java 平台的浏览器中显示这个页面时,浏览器会将 applet 类文件下载到客户机计算机,并调用 init 方法,在本示例中,它开始 RMI 通信。
<HML>
<title>Meeting Client Applet</title>
...
<applet code="MeetingClientApplet" width=500 height=120>
</appleet>
</HTML>
下一步任务是研究如何部署 RMI 代码。我们将研究两种情况:部署客户机独立应用程序和部署客户机 applet
部署概述
第 19 页(共23 页)
部署 RMI 程序所包括的步骤有:
  • 启动运行在 RMI 对象服务器上的 RMI 注册表程序。
  • 启动对象服务器主程序。
  • 设置 Web 或 ftp 服务器以便 RMI 运行时能够如先前所述那样下载类文件。
  • 运行客户机应用程序或在浏览器中显示带有 applet 的 HTML 页面。
让我们更详细地研究每个步骤。
启动 RMI 注册表
第 20 页(共23 页)
部署的第一步是启动对象服务器计算机上的 RMI 命名注册表。Java Development Kit 中包含了 rmiregistry 命令。
下图显示了在一个单独窗口中启动注册表的 Microsoft Windows 命令(start rmiregistry)。有关特定的指示,请参阅您的操作系统文档。
注册表程序管理从名称到对象引用的映射,以便程序能够调用 Naming.bind 来注册名称,以及调用 Naming.lookup 来检索引用。
一个告诫:当启动注册表时,请确保有效的类路径没有引用服务器上的存根类文件。如果引用的话,则 RMI 基础结构不会自动将存根下载到客户机计算机。

启动对象服务器
第 21 页(共23 页)
启动注册表之后,接下来我们可以启动服务器计算机上的 RMI 对象服务器主程序。和启动注册表类似,这一步与客户机是 applet 还是应用程序无关。
请记住我们编码 main 方法,以创建对象并将对象的名称注册到 RMI 注册表中。下图显示了包含 MeetingServer 远程对象及其框架的 MeetingServer 虚拟机。(java -Djava.rmi.server.codebase=http://mywebserver.com/ -Djava.security.policy=mypolicy MeetingServer)
对象服务器虚拟机需要访问图中显示的所有类文件:MeetingServer 主程序、框架类文件和接口类文件。因此,在启动服务器程序之前,您必须确保所有这些文件都在类路径中。存根类文件必须安装在 Web 服务器上;注:我们在命令中引用 Web 服务器以启动对象服务器主程序。在本示例中,代码库特性仅指定主机名,而没有指定目录,所以 Web 服务器将从其缺省目录提供服务。有关如何设置目录的更多详细信息,请参阅 Web 服务器文档。
从 JDK 1.2 开始,RMI 服务器还需要引用一个策略文件,该文件授予或拒绝远程对象的权限。在本示例中,我们创建了一个授予所有权限的策略文件;对象这样的样本程序而言,这没有问题,但对于生产环境这样做可能太危险了。注:当启动对象服务器时,必须指定策略文件。
和以前一样,本图显示了用来运行对象服务器 main 程序的 Microsoft Windows 命令。有关具体细节,请参阅操作系统文档。

运行客户机应用程序
第 22 页(共23 页)
现在,让我们研究运行客户机程序。我们将从研究作为独立应用程序的客户机开始,然后再讨论客户机 applet。
客户机计算机上的虚拟机需要访问客户机程序本身的类文件和远程接口的类文件;必须在客户机类路径中安装这些文件。客户机还需要访问存根类,但不必在客户机上安装这个文件。RMI 运行时会自动按需下载存根类。请记住,为了利用这个特性,我们需要在客户机应用程序中安装安全性管理器。因此我们可以将存根类文件放置在运行 Web 服务器的计算机上,而不是安装在客户机上。
当我们运行客户机应用程序时(请参阅命令提示 java -Djava.rmi.server.codebase=http://mywebserver.com/ -Djava.security.policy=mypolicy MeetingClient),指定代码库特性来指明去哪里可以找到存根类文件。如果您不希望动态下载存根或没有 Web 服务器,那么可以将存根类文件手工复制到客户机计算机上的类路径。
注:一旦下载了存根,与 RMI 服务器的所有进一步的交互就都使用 RMI 协议。这包含命名注册表中的查找和远程方法调用和返回。
正如我们在服务器中看到的那样,在 JDK 1.2 和更高版本中,客户机还需要使用策略文件。这里,我们将服务器上所使用的策略文件复制到了客户机上,所以可以方便地在命令行上引用它。
注:使用 ftp 协议而不是 http 来将存根下载到客户机,这也是有可能的。有关详细信息,请参阅 JDK 文档。

运行客户机 applet
第 23 页(共23 页)
运行客户机 applet 和运行独立应用程序颇有些不同。一个差异是您通常从运行在客户机计算机上的浏览器内运行 applet。当然,浏览器需要支持 Java 平台。
另一个差异是因为 applet 已经支持下载类文件,所以除了要重装浏览器之外,不必在客户机计算机上预安装任何东西。与应用程序不同,您可以将所有必需的 HTML 文件和类文件都放置在 Web 服务器上,applet 和 RMI 基础结构会按需下载这些文件。我们也不必关心客户机上的策略文件。
还有另一个关于 applet 的复杂问题:如果希望对象服务器和 Web 服务器驻留在不同的主机上,则需要了解对 applet 进行数字签名。那是因为,缺省情况下,浏览器安全性管理器仅允许网络访问(包括 RMI 方法)返回到 Web 服务器。也就是说,除非对 applet 进行了签名,否则,如果将对象分发到不是 Web 服务器的服务器上,您将会看到安全性违例。数字签名这个主题已经超出了这个简短课程的范围之外。
最后,应该注意,实际上,有可能编写独立应用程序,以便可以按需下载几乎所有东西,类似于这里所讨论的 applet。要获取更多详细信息,请参考关于自举应用程序的 JDK 文档。
关于 RMI,我们已经讨论得够多了,足够让您开始编写和部署自己的分布式程序。现在,让我们研究用于分布式对象的另一种基础结构:CORBA 方法。
posted on 2005-06-15 11:40 笨笨 阅读(2778) 评论(2)  编辑  收藏 所属分类: J2EEALL

评论

# re: Java 远程方法调用(RMI)[未登录] 2011-10-24 10:48 匿名
干嘛藏这么深?资源应该共享的是吧。!  回复  更多评论
  

# re: Java 远程方法调用(RMI) 2013-04-12 16:02 nx
if (r instanceof Meeting )
这个Meeting从哪里来?  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: