JavaRMI编程:原理、实现与应用
立即解锁
发布时间: 2025-08-20 00:52:11 订阅数: 19 


Java网络编程入门与实践
### Java RMI 编程:原理、实现与应用
#### 1. RMI 概述
在分布式环境中,我们常常需要调用位于其他系统上的远程对象的方法。远程方法调用(Remote Method Invocation,RMI)为我们提供了一种与平台无关的方式来实现这一需求。在 RMI 中,网络编程所需的流和套接字的细节被隐藏起来,对于 Java 程序员来说,远程对象的调用几乎就像调用本地对象一样透明。一旦获取了远程对象的引用,就可以像调用本地对象的方法一样调用该对象的方法。实际上,RMI 会利用字节流来传输数据和方法调用,但这些操作都由 RMI 基础设施自动处理。
RMI 从 Java 语言最早版本开始就是其核心组件,并且自最初的规范发布以来经历了一些演变。
#### 2. 基本 RMI 流程
获取远程对象引用的过程实际上比简单描述的要复杂一些。控制远程对象的服务器程序会向命名服务注册一个接口,这样客户端程序就可以访问该接口。这个接口包含了服务器希望公开的对象方法的签名。客户端程序可以使用相同的命名服务以存根(stub)的形式获取该接口的引用。存根实际上是远程对象的本地代理。在远程系统上,会有另一个代理,称为骨架(skeleton)。
当客户端程序调用远程对象的方法时,对客户端来说,就好像是直接在对象上调用该方法。但实际上,是在存根中调用了等效的方法。存根然后将调用和任何参数转发到远程机器上的骨架。只有基本类型和实现了 `Serializable` 接口的引用类型才能用作参数,这个参数序列化的过程称为编组(marshalling)。
骨架接收到字节流后,将其转换回原始的方法调用和相关参数,这个参数反序列化的过程称为解组(unmarshalling)。最后,骨架在服务器上调用方法的实现。如果方法有返回值,则上述过程会反向进行,返回值在服务器上由骨架序列化,在客户端由存根反序列化。
下面是 RMI 调用过程的 mermaid 流程图:
```mermaid
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(客户端):::process -->|方法调用| B(存根):::process
B -->|转发调用和参数| C(骨架):::process
C -->|调用方法实现| D(服务器):::process
D -->|返回值| C
C -->|返回值| B
B -->|返回值| A
```
#### 3. 实现细节
实现 RMI 客户端 - 服务器应用程序使用的包有 `java.rmi`、`java.rmi.server` 和 `java.rmi.registry`,不过通常只需要显式使用前两个包。基本步骤如下:
1. **创建接口**
2. **定义实现该接口的类**
3. **创建服务器进程**
4. **创建客户端进程**
##### 简单示例
这个示例应用程序只是向使用命名服务中注册的适当接口调用服务器上关联方法实现的任何客户端显示一条问候语。
**步骤 1:创建接口**
```java
import java.rmi.*;
public interface Hello extends Remote
{
public String getGreeting() throws RemoteException;
}
```
**步骤 2:定义实现该接口的类**
```java
import java.rmi.*;
import java.rmi.server.*;
public class HelloImpl extends UnicastRemoteObject implements Hello
{
public HelloImpl() throws RemoteException
{
//No action needed here.
}
public String getGreeting() throws RemoteException
{
return ("Hello there!");
}
}
```
**步骤 3:创建服务器进程**
```java
import java.rmi.*;
public class HelloServer
{
private static final String HOST = "localhost";
public static void main(String[] args) throws Exception
{
HelloImpl temp = new HelloImpl();
String rmiObjectName = "rmi://" + HOST + "/Hello";
Naming.rebind(rmiObjectName,temp);
System.out.println("Binding complete…\n");
}
}
```
**步骤 4:创建客户端进程**
```java
import java.rmi.*;
public class HelloClient
{
private static final String HOST = "localhost";
public static void main(String[] args)
{
try
{
Hello greeting = (Hello)Naming.lookup("rmi://" + HOST + "/Hello");
System.out.println("Message received: " + greeting.getGreeting());
}
catch(ConnectException conEx)
{
System.out.println("Unable to connect to server!");
System.exit(1);
}
catch(Exception ex)
{
ex.printStackTrace();
System.exit(1);
}
}
}
```
#### 4. 编译和执行
编译和执行上述应用程序需要以下步骤:
1. **使用 `javac` 编译所有文件**
```bash
javac Hello.java
javac HelloImpl.java
javac HelloServer.java
javac HelloClient.java
```
在 Java SE 5 之前,还需要使用 `rmic` 编译器编译实现类以生成存根和骨架文件,但现在这个步骤不再需要。
2. **启动 RMI 注册表**
```bash
rmiregistry
```
执行此命令后,唯一的变化可能是命令窗口的标题。
3. **打开一个新窗口并运行服务器**
```bash
java HelloServer
```
4. **打开第三个窗口并运行客户端**
```bash
java HelloClient
```
服务器进程和 RMI 注册表在客户端进程结束后会继续运行,需要在各自的窗口中输入 `Ctrl - C` 来关闭它们。
#### 5. 有意义地使用 RMI
在实际的 RMI 应用程序中,通常会使用多个方法和多个对象。有两种可能的策略可以采用:
- **策略一**:使用实现类的单个实例来保存需要远程调用方法的类的实例,将这些类的实例作为实现类构造函数的参数传递。
- **策略二**:直接使用实现类来存储所需的数据和方法,创建该类的实例,而不是使用单独的类。
下面通过一个银行账户应用程序的例子来分别说明这两种策略。
##### 策略一
在这种方法中,需要创建一个名为 `Account` 的应用程序类来封装单个账户的实例变量和相关方法。
**步骤 1:创建接口**
```java
import java.rmi.*;
import java.util.ArrayList;
public interface Bank1 extends Remote
{
public ArrayList<Account> getBankAccounts() throws RemoteException;
}
```
**步骤 2:定义实现类**
```java
import java.rmi.*;
import java.rmi.server.*;
import java.util.ArrayList;
public class Bank1Impl extends UnicastRemoteObject implements Bank1
{
private ArrayList<Account> acctInfo;
public Bank1Impl(ArrayList<Account> acctVals) throws RemoteException
```
0
0
复制全文
相关推荐










