2 分布式对象模型


2.1 分布式对象应用

RMI应用通常由两个独立的程序组成:服务器和客户端。典型的服务器应用程序创建多个远程对象,使这些远程对象的引用可访问,并等待客户端调用这些远程对象的方法。典型的客户端应用程序获取服务器中一个或多个远程对象的远程引用,然后调用这些对象的方法。RMI提供了服务器和客户端进行通信并相互传递信息的机制。这样的应用有时被称为分布式对象应用。

分布式对象应用需要:

下面的插图描述了一个使用注册表获取远程对象引用的RMI分布式应用程序。服务器调用注册表将名称与远程对象关联。客户端在服务器的注册表中按名称查找远程对象,然后调用其方法。插图还显示了RMI系统使用现有的Web服务器从服务器到客户端以及从客户端到服务器加载用Java编程语言编写的类的字节码的过程,以及在需要时加载类字节码。RMI可以使用Java平台支持的任何URL协议(例如HTTP、FTP、文件等)加载类字节码。

这幅插图描述了一个使用注册表获取远程对象引用的RMI分布式应用程序。

2.2 术语定义

在Java SE平台的分布式对象模型中,远程对象是可以从另一个Java虚拟机调用其方法的对象,可能位于不同的主机上。这种类型的对象由一个或多个远程接口描述,这些接口是用Java编程语言编写的声明远程对象的方法。

远程方法调用(RMI)是在远程对象上调用远程接口的方法。最重要的是,对远程对象的方法调用与对本地对象的方法调用具有相同的语法。

2.3 分布式和非分布式模型对比

Java SE平台的分布式对象模型与Java SE平台的对象模型在以下方面类似:

Java SE平台的分布式对象模型与Java SE平台的对象模型在以下方面不同:

2.4 RMI接口和类概述

负责指定RMI系统远程行为的接口和类定义在java.rmi包层次结构中。以下图显示了几个这些接口和类之间的关系:

几个这些接口和类之间的关系

2.4.1 java.rmi.Remote接口

在RMI中,远程接口是一个声明一组可以从远程Java虚拟机调用的方法的接口。远程接口必须满足以下要求:

接口java.rmi.Remote是一个标记接口,不定义任何方法:

public interface Remote {}

远程接口必须至少扩展接口java.rmi.Remote(或另一个扩展java.rmi.Remote的远程接口)。但是,远程接口可以在以下条件下扩展非远程接口:

例如,下面的接口BankAccount为访问银行账户定义了一个远程接口。它包含用于向账户存款、获取账户余额和从账户取款的远程方法:

public interface BankAccount extends java.rmi.Remote {
        public void deposit(float amount)
                throws java.rmi.RemoteException;
        public void withdraw(float amount)
                throws OverdrawnException, java.rmi.RemoteException;
        public float getBalance()
                throws java.rmi.RemoteException;
}

下一个示例显示了一个有效的远程接口Beta,它扩展了一个具有远程方法的非远程接口Alpha和接口java.rmi.Remote

public interface Alpha {
        public final String okay = "constants are okay too";
        public Object foo(Object obj)
                throws java.rmi.RemoteException;
        public void bar() throws java.io.IOException;
        public int baz() throws java.lang.Exception;
}

public interface Beta extends Alpha, java.rmi.Remote {
        public void ping() throws java.rmi.RemoteException;
}

2.4.2 RemoteException

java.rmi.RemoteException类是在远程方法调用期间由RMI运行时抛出的异常的超类。为确保使用RMI系统的应用程序的健壮性,远程接口中声明的每个远程方法必须在其throws子句中指定java.rmi.RemoteException(或其超类,如java.io.IOExceptionjava.lang.Exception)。

当远程方法调用因某种原因失败时,将抛出异常java.rmi.RemoteException。远程方法调用失败的一些原因包括:

RemoteException是一个已检查异常(必须由远程方法的调用者处理,并由编译器检查),而不是RuntimeException

2.4.3 RemoteObject类及其子类

RMI服务器功能由java.rmi.server.RemoteObject及其子类java.rmi.server.RemoteServerjava.rmi.server.UnicastRemoteObject提供。

2.5 实现远程接口

实现远程接口的类的一般规则如下:

例如,以下类BankAcctImpl实现了BankAccount远程接口,并扩展了java.rmi.server.UnicastRemoteObject类:

package mypackage;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class BankAccountImpl
        extends UnicastRemoteObject
        implements BankAccount
{
        private float balance = 0.0;

        public BankAccountImpl(float initialBalance)
                throws RemoteException
        {
                balance = initialBalance;
        }
        public void deposit(float amount) throws RemoteException {
                ...
        }
        public void withdraw(float amount) throws OverdrawnException,
                RemoteException {
                ...
        }
        public float getBalance() throws RemoteException {
                ...
        }
}

注意:

2.6 远程方法调用中的参数传递

传递给远程对象的参数或返回值可以是任何可序列化的对象。这包括基本类型、远程对象和实现java.io.Serializable接口的非远程对象。有关如何使类可序列化的更多详细信息,请参阅"Java对象序列化规范"。在读取参数、返回值和异常时,RMI系统会动态下载本地不可用的类。有关RMI在读取参数、返回值和异常时如何下载参数和返回值类的更多信息,请参阅"动态类加载"部分。

2.6.1 传递非远程对象

作为远程方法调用的参数传递或作为远程方法调用的结果返回的非远程对象是通过复制传递的;也就是说,该对象使用Java SE平台的对象序列化机制进行序列化。

因此,当将非远程对象作为远程方法调用的参数或返回值传递时,在调用远程对象之前,会复制非远程对象的内容。

当从远程方法调用返回非远程对象时,在调用方虚拟机中会创建一个新对象。

2.6.2 传递远程对象

在远程方法调用中将导出的远程对象作为参数或返回值传递时,将传递该远程对象的存根。未导出的远程对象不会被替换为存根实例。作为参数传递的远程对象只能实现远程接口。

2.6.3 引用完整性

如果在单个远程方法调用中从一个JVM传递两个对对象的引用到另一个JVM(作为参数或返回值),并且这些引用指向发送JVM中的同一对象,则这些引用将在接收JVM中引用对象的单个副本。更一般地说:在单个远程方法调用中,RMI系统在作为参数或返回值传递的对象之间保持引用完整性。

2.6.4 类注释

当对象在远程方法调用中从一个JVM发送到另一个JVM时,RMI系统会在调用流中的类描述符上注释信息(URL),以便在接收方加载类。要求在远程方法调用期间按需下载类。

2.6.5 参数传输

RMI调用中的参数被写入一个java.io.ObjectOutputStream类的子类流,以便将参数序列化到远程调用的目的地。该ObjectOutputStream子类通过覆盖replaceObject方法来用存根实例替换每个导出的远程对象。对象参数通过ObjectOutputStreamwriteObject方法写入流。ObjectOutputStream通过writeObject方法写入流中的每个对象(包括被写入的对象引用的对象)调用replaceObject方法。RMI的ObjectOutputStream子类的replaceObject方法返回以下内容:

RMI的ObjectOutputStream子类还实现了annotateClass方法,该方法在调用流中注释类的位置,以便在接收方下载类。有关annotateClass的使用更多信息,请参阅"动态类加载"部分。

由于参数被写入单个ObjectOutputStream,在调用方引用同一对象的引用将在接收方引用对象的同一副本。在接收方,参数由单个ObjectInputStream读取。

ObjectOutputStream的默认写入对象行为(以及类似地,ObjectInputStream的读取对象行为)在参数传递中保持不变。例如,在写入对象时调用writeReplace,在读取对象时调用readResolve,RMI的参数编组和解组流会遵守这些行为。

类似于上述RMI中的参数传递,返回值(或异常)被写入ObjectOutputStream的子类,并具有与参数传输相同的替换行为。

2.7 定位远程对象

提供了一个简单的引导名称服务器,用于存储对远程对象的命名引用。可以使用类java.rmi.Naming的基于URL的方法存储远程对象引用。

要在客户端上调用远程对象的方法,该客户端必须首先获取对该对象的引用。对远程对象的引用通常作为方法调用的参数或返回值获得。RMI系统提供了一个简单的引导名称服务器,用于从给定主机上获取远程对象。类java.rmi.Naming提供了基于统一资源定位符(URL)的方法,用于查找、绑定、重新绑定、解绑和列出在特定主机和端口上维护的名称-对象对。