JavaSpaces与RMI快速参考
立即解锁
发布时间: 2025-08-18 02:32:36 阅读量: 2 订阅数: 6 

# JavaSpaces与RMI快速参考
## 1. JavaSpaces概述
JavaSpaces是Sun提出的一种新的分布式对象系统,它比Java中现有的RMI和对象序列化功能处于更高的层次。JavaSpaces提供了一个分布式、持久的对象系统,大致仿照早期的共享内存系统(如LINDA)构建。虽然它与并行共享内存系统(如Posix shm_xxx库和Python等并行语言中的共享内存功能)有一些相似之处,但也存在一些重要差异。
### 1.1 JavaSpaces的分布式应用范式
JavaSpaces支持的分布式应用范式中,远程代理通过共享数据对象空间间接相互交互。对象以条目的形式存储在JavaSpace中,客户端可以向空间写入条目、从空间读取条目或从空间获取条目,其基本架构如下:
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(客户端):::process -->|写| B(JavaSpace):::process
A -->|读| B
A -->|取| B
```
对JavaSpace中条目的访问通过一组基本操作实现:
- **read**:从空间中读取与模板匹配的条目。
- **write**:向空间中添加条目。
- **take**:从空间中读取并移除与模板匹配的条目。
- **notify**:如果与模板匹配的条目被添加到空间中,则通过给定的事件处理程序发送通知。通知请求有一个关联的超时时间,如果在超时时间内没有添加匹配的条目,则通知请求失败并从JavaSpace中删除。
多个基本操作可以组合成事务,将基本操作组合成一个原子聚合操作。在一个分布式应用中可以有多个客户端和多个JavaSpace,一个客户端甚至一个客户端的一个事务都可以访问多个JavaSpace。JavaSpaces规范的一个重要特性是,对给定JavaSpace的所有操作都被认为是无序的。如果有多个线程或多个远程代理对JavaSpace发出操作,并且需要对操作施加某种顺序,则需要自行同步线程或代理。
每个JavaSpace以条目的形式保存数据,每个条目有一个或多个字段,用于匹配客户端的传入请求。每个读取、获取或通知条目的请求都包含一个用于匹配条目的模板。为了使JavaSpace中的条目匹配,该条目必须与模板对象的类型相同。模板中的每个字段可以有非空值(必须与JavaSpace中匹配条目的字段匹配)或空值(匹配该字段中的任何值)。
JavaSpace上的所有操作都是“事务安全的”,这意味着每个操作或事务要么完全提交到JavaSpace,要么完全不提交。如果向JavaSpace的写入操作成功,则可以确保该条目已写入,并将在客户端对该空间的下一次读取或获取操作中出现。JavaSpace上的操作可以是简单操作的形式,也可以是单个事务中的一组操作。
需要注意的是,JavaSpaces与分布式数据库系统不同。JavaSpace知道其条目的类型,并可以比较字段值,但它不了解其条目中数据的结构,也不提供对持久数据的不透明读写访问。JavaSpace中的条目是写入该空间的对象的序列化副本,作为读取或获取操作结果返回给客户端的条目是空间中对象的单独副本。
### 1.2 Entry和EntryRep
每个JavaSpace仅由条目组成,这些条目由Entry类的实例表示。条目是一组对象引用,代表条目中的字段。当一个条目被添加到JavaSpace时,该条目通过独立序列化条目中的每个字段以序列化形式存储。因此,条目中的每个字段必须是公共的、可序列化的,并且必须是一个对象(不是基本类型)。
EntryRep充当条目进出JavaSpace的通道。在写入操作期间,它们在进入JavaSpace之前对条目进行序列化,并对作为读取、获取或通知操作结果返回的条目进行反序列化。一个给定的EntryRep可以多次写入同一个JavaSpace,这将在空间中产生多个相同的条目。
EntryRep用于在读取或获取操作中指定JavaSpace条目。客户端创建一个包含其希望在JavaSpace中匹配的值和通配符的条目,然后将其包装在EntryRep中,EntryRep生成模板条目的序列化形式,并将其作为操作的参数传递给JavaSpace。JavaSpace将模板条目的序列化字节与其自己的条目进行比较,并匹配第一个序列化字节与模板条目中非空字段的序列化字节相同的条目。
独立序列化条目的每个字段的另一个好处是,它允许从空间中进行容错检索条目。如果读取、获取或通知操作找到匹配项,并且在反序列化时发生错误,则会抛出UnusableEntryException。异常对象包含从JavaSpace成功反序列化的条目中的字段列表,以及不可用字段列表和解释每个不可用字段反序列化失败原因的嵌套异常列表。反序列化失败的一些原因包括客户端上缺少类文件,或条目中的远程引用不再有效导致的RemoteException。客户端可以以不同的方式响应UnusableEntryException:可以尝试使用接收到的部分条目,可以忽略部分条目并尝试读取或获取另一个条目,或者可以完全放弃。
由于目前Java API不支持持久服务器对象,因此将远程引用作为条目的一部分放入JavaSpace是危险的。如果远程引用背后的服务器对象由于某种原因(如服务器重启、服务器崩溃等)被销毁,则远程引用将变得无效,直到客户端尝试从JavaSpace获取该条目时才会发现。建议在条目中使用远程对象的元数据(即其远程主机和注册表名称),并让客户端自己建立与服务器对象的远程引用。
### 1.3 事务
事务是一组基本操作,对一个或多个JavaSpace作为原子操作执行。事务的原子性意味着事务中的所有操作要么全部执行,要么全部不执行。如果事务中的任何操作失败(例如,读取操作未能匹配到条目,或通知在触发前超时),则整个事务失败,任何已成功的子操作将被“回滚”,JavaSpace将保持在如果从未尝试该事务时的相同状态。
### 1.4 JavaSpace接口
JavaSpace规范还定义了一个JavaSpace类,它提供了对远程JavaSpace的接口。JavaSpace接口提供了read()、write()、take()和notify()方法,允许客户端对JavaSpace执行基本操作。每个方法接受一个EntryRep(用作要放入空间的条目或用于匹配空间中已有条目的模板)、一个可选的事务(操作应在其中执行)和一个可选的Identity(可用于根据访问控制列表验证客户端对JavaSpace条目的访问)。Identity可用于验证调用者在JavaSpace上执行给定操作的权限,可能通过检查访问控制列表来实现。目前的规范尚不清楚这个Identity参数是否会使用Java安全API中的Identity类,但很可能会使用。
#### 1.4.1 write()方法
```java
void write(EntryRep r, Transaction t, Identity i)
throws RemoteException, TransactionException, SecurityException
```
write()调用将一个EntryRep添加到JavaSpace。封闭条目中的每个字段被独立序列化,构成整个条目的序列化字节被发送到JavaSpace进行存储和后续查找。如果write()调用中包含事务,则在整个事务执行之前,新条目对JavaSpace的其他客户端不可见。如果在事务的其余过程中该条目被取走,则事务(包括写入操作)将成功,但其他客户端将永远看不到该新条目。
#### 1.4.2 read()方法
```java
EntryRep read(EntryRep template, Transaction t, Identity i)
throws RemoteException, TransactionException, SecurityException
```
如果JavaSpace中的一个条目与EntryRep模板匹配,则该方法调用将其作为EntryRep对象返回。如果read()调用中包含非空事务,则只有在整个事务成功时才会返回匹配的EntryRep。在包含事务的过程中读取的任何条目都会被放入一个待处理列表中,在读取及其事务完成(成功执行或中止)之前,其他操作或事务不能取走这些条目。如果与read()的模板条目匹配的所有条目都在未完成的事务中处于待处理状态,则会抛出TransactionConflictException。
#### 1.4.3 take()方法
```java
EntryRep take(EntryRep template, Transaction t, Identity i)
throws RemoteException, TransactionException, SecurityException
```
JavaSpace接口上的take()操作与read()操作类似,不同之处在于匹配的条目也会从空间中移除。如果Transaction参数不为空,则在事务完成之前不会返回匹配的条目。如果take()调用抛出RemoteException,则有可能条目已从空间中移除,但未完整返回。
#### 1.4.4 notify()方法
```java
EventRegID notify(EntryRep template, EventCatcher c, Transaction t,
Identity i, int timeout)
throws RemoteException, TransactionException, SecurityException
```
notify()调用用于在特定时间段内注册对匹配条目的兴趣。如果在通知请求过期之前有匹配的条目写入空间,则会调用给定EventCatcher上的notify()方法。返回给客户端的EventRegID对象包含一组长整型值,包括随通知一起到来的事件ID、可用于续订或取消通知的cookie值,以及JavaSpace分配给通知请求的实际超时时间。如果将非空事务传递给方法调用,则在包含事务的持续时间内,事件捕获器将收到匹配条目的通知。在事务结束时,通知请求将从空间中删除。
JavaSpace接口还包括以下两个方法,用于控制之前向空间发出的通知请求:
#### 1.4.5 renew()方法
```java
long renew(long cookie, long extension)
throws RemoteException, NotRegisteredException
```
此方法允许客户端请求延长通知请求的时间。cookie参数是原始notify()调用的EventRegID中返回的值,extension是希望延长的注册通知的时间。该方法返回JavaSpace实际授予的时间延长,如果有的话。这些时间值的单位在JavaSpaces规范中尚未详细说明。
#### 1.4.6 cancel()方法
```java
void cancel(long cookie) throws RemoteException, NotRegisteredException
```
与cookie关联的通知请求被取消。
## 2. RMI快速参考
RMI(Remote Method Invocation)是JDK 1.1中包含的远程对象包。RMI API包含在java.rmi包中,该包包括三个主要子包:java.rmi.dgc、java.rmi.registry和java.rmi.server。这里不包括java.rmi.dgc包,因为它是RMI实现的内部包,与分布式垃圾回收有关,普通读者通常不会直接使用该包。
### 2.1 java.rmi包
RMI的核心包包含Remote接口、Naming类和RMISecurityManager类。这些接口被RMI客户端和服务器用于定义远程接口、在网络上查找它们并安全地使用它们。此外,这个核心包包含了在远程对象查找和远程方法调用期间使用的许多基本RMI异常类型。
| 异常类 | 描述 |
| --- | --- |
| AccessException | 由在Naming或Registry接口上执行不当操作的尝试引起的RemoteException。注册表只允许本地请求绑定、重新绑定或取消绑定对象,因此在远程注册表上调用这些方法的尝试会导致AccessException。 |
| AlreadyBoundException | 当尝试将对象绑定到已绑定的名称时抛出的异常。 |
| ConnectException | 在远程方法调用期间,当远程主机拒绝连接时抛出的RemoteException。 |
| ConnectIOException | 在尝试进行远程方法调用时发生I/O错误时抛出的RemoteException。 |
| MarshalException | 在尝试编组远程方法调用的任何部分(头数据或方法参数)时发生I/O错误时抛出的RemoteException。 |
| Naming | 这是RMI注册表中命名服务的主要应用程序接口。通过lookup()方法获取远程对象的引用。使用bind()和rebind()方法将本地对象实现绑定到本地注册表中的名称。使用unbind()方法从名称注册表中移除本地绑定的对象。使用list()方法可以获取当前存储在注册表中的所有对象的名称。 |
| NoSuchObjectException | 当尝试对不再可用的远程对象调用方法时抛出的RemoteException。 |
| NotBoundException | 当尝试使用没有绑定对象的名称进行查找时抛出的异常。 |
| Remote | 每个远程对象都必须实现此接口,任何打算远程调用的方法都必须在Remote接口中定义。这是一个占位符接口,用于标识所有远程对象,但本身不定义任何方法。 |
| RemoteException | 在任何远程对象操作期间发生错误时抛出的IOException。RemoteException包含一个Throwable数据成员,表示导致RemoteException抛出的嵌套异常。 |
| RMISecurityException | 当RMISecurityManager在远程操作期间检测到安全违规时抛出的SecurityException。 |
| RMISecurityManager | 通过重写SecurityManager中所有相关的访问检查方法,为作为远程对象存根加载的类强制执行安全策略。默认情况下,存根对象仅允许执行类定义和类访问操作。如果本地安全管理器不是RMISecurityManager(使用System.setSecurityManager()方法),则存根类只能从本地文件系统加载。 |
| ServerError | 服务器在执行远程方法时发生的错误。继承自RemoteException的嵌套Throwable数据成员包含生成该错误的服务器端异常。 |
| ServerException | 服务器在执行远程方法时发生的异常。继承自RemoteException的嵌套Throwable数据成员包含生成该异常的服务器端异常。 |
| ServerRuntimeException | 服务器在执行远程方法时发生的RemoteException。 |
| StubNotFoundException | 当对象被导出以参与远程RMI调用时,或者在远程方法调用期间可能发生的异常。在服务器导出时,如果由于某种原因找不到或无法使用对象的存根类(例如,存根类不在服务器进程的CLASSPATH中,或存根类无法实例化),则会抛出此异常。在远程方法调用期间,如果远程对象未完全或正确导出,客户端可能会收到此异常。 |
| UnexpectedException | 如果在从远程方法调用返回期间遇到未在远程方法签名中指定的异常,则抛出的异常。意外异常可能发生在服务器端或客户端。继承自RemoteException的嵌套Throwable对象包含实际发生的异常。 |
| UnknownHostException | 在Naming查找期间指定的主机无法找到时抛出的RemoteException。 |
| UnmarshalException | 在解组远程方法调用的返回值时发生错误时抛出的RemoteException。错误的来源可能是在从服务器向客户端发送头或返回值时发生的I/O错误,或者是找不到返回对象的类。 |
以下是部分异常类的代码示例:
```java
// AccessException类
public class AccessException extends java.rmi.RemoteException {
// Public constructors
public AccessException(String descr) {
super(descr);
}
public AccessException(String descr, Exception detail) {
super(descr, detail);
}
}
// AlreadyBoundException类
public class AlreadyBoundException extends java.lang.Exception {
// Public constructors
public AlreadyBoundException() {
super();
}
public AlreadyBoundException(String descr) {
super(descr);
}
}
```
### 2.2 java.rmi.registry包
此部分内容暂未详细展开,但java.rmi.registry包在RMI中主要用于处理注册表相关的操作,例如对象的绑定、查找等,是RMI系统中重要的组成部分,后续可能会在实际应用中发挥关键作用。
综上所述,JavaSpaces和RMI都是Java中用于分布式计算的重要技术。JavaSpaces提供了一种基于共享数据对象空间的分布式应用范式,而RMI则允许在不同Java虚拟机之间进行远程方法调用。通过对它们的了解和掌握,可以更好地开发分布式Java应用程序。
### 2.3 java.rmi.server包
`java.rmi.server` 包包含了一系列用于实现远程对象的类和接口,这些类和接口为 RMI 服务器端的开发提供了支持。以下是该包中一些重要的类和接口:
| 类/接口 | 描述 |
| --- | --- |
| RemoteObject | 这是所有远程对象的基类,实现了 `Remote` 接口。它提供了一些基本的方法,用于处理远程对象的引用和序列化。 |
| UnicastRemoteObject | 用于创建单播远程对象的类。通过继承 `UnicastRemoteObject` 类,可以方便地创建一个可以在网络上被远程调用的对象。 |
| RMISocketFactory | 用于创建 RMI 所用的套接字工厂,允许开发者自定义 RMI 通信时使用的套接字。 |
| RemoteServer | 是 `UnicastRemoteObject` 的基类,提供了一些服务器端的基础功能。 |
以下是 `UnicastRemoteObject` 类的简单使用示例:
```java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 定义远程接口
interface MyRemoteInterface extends Remote {
String sayHello() throws RemoteException;
}
// 实现远程接口
class MyRemoteObject extends UnicastRemoteObject implements MyRemoteInterface {
protected MyRemoteObject() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "Hello, RMI!";
}
}
```
在这个示例中,首先定义了一个远程接口 `MyRemoteInterface`,其中包含一个远程方法 `sayHello`。然后创建了一个实现该接口的类 `MyRemoteObject`,它继承自 `UnicastRemoteObject`。通过这种方式,`MyRemoteObject` 就成为了一个可以被远程调用的对象。
### 2.4 RMI 的使用流程
使用 RMI 进行远程方法调用通常需要以下几个步骤:
1. **定义远程接口**:创建一个继承自 `Remote` 接口的接口,其中定义了可以被远程调用的方法。这些方法必须声明抛出 `RemoteException`。
```java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyRemoteService extends Remote {
int add(int a, int b) throws RemoteException;
}
```
2. **实现远程接口**:创建一个类来实现上述定义的远程接口,并继承 `UnicastRemoteObject` 类。
```java
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class MyRemoteServiceImpl extends UnicastRemoteObject implements MyRemoteService {
protected MyRemoteServiceImpl() throws RemoteException {
super();
}
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
```
3. **启动 RMI 注册表**:使用 `rmiregistry` 命令启动 RMI 注册表,它用于存储和管理远程对象的引用。
```bash
rmiregistry
```
4. **注册远程对象**:在服务器端,将实现了远程接口的对象绑定到 RMI 注册表中。
```java
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
public static void main(String[] args) {
try {
// 创建远程对象
MyRemoteService service = new MyRemoteServiceImpl();
// 启动 RMI 注册表
LocateRegistry.createRegistry(1099);
// 将远程对象绑定到注册表
Naming.rebind("rmi://localhost:1099/MyRemoteService", service);
System.out.println("Remote object registered.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
5. **查找并调用远程对象**:在客户端,通过 RMI 注册表查找远程对象,并调用其方法。
```java
import java.rmi.Naming;
public class RMIClient {
public static void main(String[] args) {
try {
// 查找远程对象
MyRemoteService service = (MyRemoteService) Naming.lookup("rmi://localhost:1099/MyRemoteService");
// 调用远程方法
int result = service.add(2, 3);
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
### 2.5 RMI 与 JavaSpaces 的对比与结合
#### 对比
- **数据交互方式**:RMI 主要基于远程方法调用,客户端直接调用服务器端的方法,侧重于方法的执行和结果的返回;而 JavaSpaces 基于共享数据对象空间,客户端通过读写条目来间接交互,更注重数据的共享和传递。
- **事务处理**:RMI 本身没有内置的事务机制,需要开发者自行实现;JavaSpaces 则支持事务操作,多个基本操作可以组合成事务,保证操作的原子性。
- **扩展性**:RMI 适用于简单的远程方法调用场景,对于复杂的分布式系统,可能需要复杂的架构来实现扩展性;JavaSpaces 提供了分布式、持久的对象系统,更适合构建大规模的分布式应用。
#### 结合
在实际的分布式应用中,可以将 RMI 和 JavaSpaces 结合使用。例如,使用 RMI 来实现一些快速的远程方法调用,处理一些实时性要求较高的任务;同时使用 JavaSpaces 来实现数据的共享和异步处理,处理一些对数据一致性和持久性要求较高的任务。以下是一个简单的结合示例:
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(客户端):::process -->|RMI 调用| B(服务器):::process
B -->|写入条目| C(JavaSpace):::process
D(其他客户端):::process -->|读取条目| C
```
在这个示例中,客户端通过 RMI 调用服务器的方法,服务器将处理结果以条目的形式写入 JavaSpace,其他客户端可以从 JavaSpace 中读取这些条目,实现数据的共享和异步处理。
### 总结
JavaSpaces 和 RMI 都是 Java 中强大的分布式计算技术,它们各自具有独特的特点和优势。JavaSpaces 提供了一种基于共享数据对象空间的分布式应用范式,支持事务操作,适合构建大规模的分布式系统;RMI 则允许在不同 Java 虚拟机之间进行远程方法调用,简单易用,适用于实时性要求较高的场景。通过对它们的深入理解和灵活运用,可以根据具体的应用需求选择合适的技术,或者将它们结合使用,开发出高效、稳定的分布式 Java 应用程序。在实际开发中,还需要考虑性能优化、安全管理等方面的问题,以确保系统的可靠性和安全性。
0
0
复制全文