Java Rmi 简明教程

Java RMI - Quick Guide

Java RMI - Introduction

RMI 代表 Remote Method Invocation 。它是一种允许驻留在一个系统(JVM)中的对象访问/调用运行在另一个 JVM 上的对象的机制。

RMI 用于构建分布式应用程序;它提供 Java 程序之间的远程通信。它在包 java.rmi 中提供。

Architecture of an RMI Application

在 RMI 应用程序中,我们编写两个程序,一个 server program (驻留在服务器上)和一个 client program (驻留在客户端上)。

  1. 在服务器程序内部,创建远程对象,并将该对象的引用提供给客户端(使用注册表)。

  2. 客户端程序请求服务器上的远程对象并尝试调用其方法。

下图显示了 RMI 应用程序的架构。

rmi architecture

现在让我们讨论此架构的组件。

  1. Transport Layer − 此层连接客户端和服务器。它管理现有连接,并设置新连接。

  2. Stub − 存根是客户端处远程对象的表示(代理)。它驻留在客户端系统中;它充当客户端程序的网关。

  3. Skeleton − 这是驻留在服务器端的对象。 stub 与此骨架通信以将请求传递到远程对象。

  4. RRL(Remote Reference Layer) − 它是管理客户端对远程对象所做引用的层。

Working of an RMI Application

以下要点总结了 RMI 应用程序的工作原理 −

  1. 当客户端向远程对象发出调用时,它会被存根接收,然后存根最终会将此请求传递给 RRL。

  2. 当客户端 RRL 接收请求时,它会调用对象 remoteRef 的名为 invoke() 的方法。它将请求传递给服务器端的 RRL。

  3. 服务器端的 RRL 将请求传递给骨架(服务器上的代理),最终在服务器上调用所需对象。

  4. 结果会一路传递回客户端。

Marshalling and Unmarshalling

每当客户端调用远程对象上接受参数的方法时,参数都会被捆绑到消息中,然后通过网络发送。这些参数可能是基本类型或对象。如果是基本类型,则将参数放在一起并为其附加标题。如果是对象,则对它们进行序列化。此过程称为 marshalling

在服务器端,已打包的参数将被解绑,然后调用所需的方法。此过程称为 unmarshalling

RMI Registry

RMI 注册表是在其上放置所有服务器对象的命名空间。每次服务器创建对象时,它都会使用 RMIregistry(使用 bind()reBind() 方法)注册此对象。它们使用称为 bind name 的唯一名称进行注册。

要调用远程对象,客户端需要该对象的引用。那时,客户端使用其绑定名称(使用 lookup() 方法)从注册表中获取对象。

以下插图对整个过程进行了说明 −

registry

Goals of RMI

以下是 RMI 的目标 −

  1. 最大程度地减少应用程序的复杂性。

  2. To preserve type safety.

  3. Distributed garbage collection.

  4. 最大程度地减少使用本地对象和远程对象之间的差异。

Java RMI Application

要编写 RMI Java 应用程序,您需要按照以下步骤进行操作 −

  1. Define the remote interface

  2. 开发实现类(远程对象)

  3. Develop the server program

  4. Develop the client program

  5. Compile the application

  6. Execute the application

Defining the Remote Interface

远程接口提供了特定远程对象的所有方法的描述。客户端与此远程接口进行通信。

要创建远程接口 −

  1. 创建一个扩展预定义接口 Remote (该接口属于此包)的接口。

  2. 在此接口中声明客户端可以调用的所有业务方法。

  3. 由于远程调用期间可能会出现网络问题,因此可能会出现名为 RemoteException 的异常;引发此异常。

以下是远程界面的示例。在这里,我们定义了一个名为 Hello 的界面,它有一个称为 printMsg() 的方法。

import java.rmi.Remote;
import java.rmi.RemoteException;

// Creating Remote interface for our application
public interface Hello extends Remote {
   void printMsg() throws RemoteException;
}

Developing the Implementation Class (Remote Object)

我们需要实现较早步骤中创建的远程界面。(我们可以单独编写一个实现类,或者我们可以直接让服务器程序实现此界面。)

要开发一个实现类 -

  1. 实现前一步骤中创建的界面。

  2. 为远程界面的所有抽象方法提供实现。

以下是实现类。在这里,我们创建了一个名为 ImplExample 的类,并实现了前一步骤中创建的界面 Hello 并为此方法提供了 body ,该方法打印一条消息。

// Implementing the remote interface
public class ImplExample implements Hello {

   // Implementing the interface method
   public void printMsg() {
      System.out.println("This is an example RMI program");
   }
}

Developing the Server Program

RMI 服务器程序应该实现远程界面或扩展实现类。在这里,我们应该创建一个远程对象并将它绑定到 RMIregistry

要开发一个服务器程序 -

  1. 创建一个客户端类,从中你可以调用远程对象。

  2. 在实例化实现类时通过 Create a remote object 调用它,如下所示。

  3. 使用属于包 java.rmi.server 的名为 UnicastRemoteObject 的类的 exportObject() 方法导出远程对象。

  4. 使用属于包 java.rmi.registryLocateRegistry 类的 getRegistry() 方法获取 RMI 注册表。

  5. 使用名为 Registry 的类的 bind() 方法将创建的远程对象绑定到注册表。对此方法,将表示绑定名称和已导出的对象的一个字符串作为参数传进来。

以下是 RMI 服务器程序的示例。

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Server extends ImplExample {
   public Server() {}
   public static void main(String args[]) {
      try {
         // Instantiating the implementation class
         ImplExample obj = new ImplExample();

         // Exporting the object of implementation class
         // (here we are exporting the remote object to the stub)
         Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);

         // Binding the remote object (stub) in the registry
         Registry registry = LocateRegistry.getRegistry();

         registry.bind("Hello", stub);
         System.err.println("Server ready");
      } catch (Exception e) {
         System.err.println("Server exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Developing the Client Program

在其中编写一个客户端程序,用这个对象获取远程对象并调用所需的方法。

要开发一个客户端程序 -

  1. 创建一个客户端类,从中你打算调用远程对象。

  2. 使用属于包 java.rmi.registryLocateRegistry 类的 getRegistry() 方法获取 RMI 注册表。

  3. 使用属于包 java.rmi.registry 的类 Registrylookup() 方法从注册表中获取对象。对此方法,你需要将表示绑定名称的字符串值作为参数传入。这会返回远程对象。

  4. 查找() 返回一个远程类型的对象,将它向下转换为 Hello 类型。

  5. 最后使用获得的远程对象调用所需方法。

下面是一个 RMI 客户端程序示例。

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
   private Client() {}
   public static void main(String[] args) {
      try {
         // Getting the registry
         Registry registry = LocateRegistry.getRegistry(null);

         // Looking up the registry for the remote object
         Hello stub = (Hello) registry.lookup("Hello");

         // Calling the remote method using the obtained object
         stub.printMsg();

         // System.out.println("Remote method invoked");
      } catch (Exception e) {
         System.err.println("Client exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Compiling the Application

要编译应用程序-

  1. Compile the Remote interface.

  2. Compile the implementation class.

  3. Compile the server program.

  4. Compile the client program.

或者,

打开存储所有程序的文件夹,然后按如下所示编译所有 Java 文件。

Javac *.java
stored programs

Executing the Application

Step 1 - 使用以下命令启动 rmi 注册表。

start rmiregistry
start execution

这会在一个单独的窗口中启动一个 rmi 注册表,如下所示。

separate window

Step 2 - 按如下所示运行服务器类文件。

Java Server
run server

Step 3 - 按如下所示运行客户端类文件。

java Client
run client

Verification - 在启动客户端后,您很快就会在服务器中看到以下输出。

output

Java RMI - GUI Application

在前面的章节中,我们创建了一个示例 RMI 应用程序。在本章中,我们将说明如何创建 RMI 应用程序,其中客户端调用显示 GUI 窗口(JavaFX)的方法。

Defining the Remote Interface

在此,我们定义一个名为 Hello 的远程接口,其中包含一个名为 animation() 的方法。

import java.rmi.Remote;
import java.rmi.RemoteException;

// Creating Remote interface for our application
public interface Hello extends Remote {
   void animation() throws RemoteException;
}

Developing the Implementation Class

在此应用程序的实现类(远程对象)中,我们尝试使用 JavaFX 创建一个显示 GUI 内容的窗口。

import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.event.EventHandler;

import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;

import javafx.scene.shape.Box;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;

import javafx.stage.Stage;
import javafx.util.Duration;

// Implementing the remote interface
public class FxSample extends Application implements Hello {
   @Override
   public void start(Stage stage) {
      // Drawing a Box
      Box box = new Box();

      // Setting the properties of the Box
      box.setWidth(150.0);
      box.setHeight(150.0);
      box.setDepth(100.0);

      // Setting the position of the box
      box.setTranslateX(350);
      box.setTranslateY(150);
      box.setTranslateZ(50);

      // Setting the text
      Text text = new Text(
         "Type any letter to rotate the box, and click on the box to stop the rotation");

      // Setting the font of the text
      text.setFont(Font.font(null, FontWeight.BOLD, 15));

      // Setting the color of the text
      text.setFill(Color.CRIMSON);

      // Setting the position of the text
      text.setX(20);
      text.setY(50);

      // Setting the material of the box
      PhongMaterial material = new PhongMaterial();
      material.setDiffuseColor(Color.DARKSLATEBLUE);

      // Setting the diffuse color material to box
      box.setMaterial(material);

      // Setting the rotation animation to the box
      RotateTransition rotateTransition = new RotateTransition();

      // Setting the duration for the transition
      rotateTransition.setDuration(Duration.millis(1000));

      // Setting the node for the transition
      rotateTransition.setNode(box);

      // Setting the axis of the rotation
      rotateTransition.setAxis(Rotate.Y_AXIS);

      // Setting the angle of the rotation
      rotateTransition.setByAngle(360);

      // Setting the cycle count for the transition
      rotateTransition.setCycleCount(50);

      // Setting auto reverse value to false
      rotateTransition.setAutoReverse(false);

      // Creating a text filed
      TextField textField = new TextField();

      // Setting the position of the text field
      textField.setLayoutX(50);
      textField.setLayoutY(100);

      // Handling the key typed event
      EventHandler<KeyEvent> eventHandlerTextField = new EventHandler<KeyEvent>() {
         @Override
         public void handle(KeyEvent event) {
            // Playing the animation
            rotateTransition.play();
         }
      };

      // Adding an event handler to the text feld
      textField.addEventHandler(KeyEvent.KEY_TYPED, eventHandlerTextField);

      // Handling the mouse clicked event(on box)
      EventHandler<javafx.scene.input.MouseEvent> eventHandlerBox =
         new EventHandler<javafx.scene.input.MouseEvent>() {
         @Override
         public void handle(javafx.scene.input.MouseEvent e) {
            rotateTransition.stop();
         }
      };

      // Adding the event handler to the box
      box.addEventHandler(javafx.scene.input.MouseEvent.MOUSE_CLICKED, eventHandlerBox);

      // Creating a Group object
      Group root = new Group(box, textField, text);

      // Creating a scene object
      Scene scene = new Scene(root, 600, 300);

      // Setting camera
      PerspectiveCamera camera = new PerspectiveCamera(false);
      camera.setTranslateX(0);
      camera.setTranslateY(0);
      camera.setTranslateZ(0);
      scene.setCamera(camera);

      // Setting title to the Stage
      stage.setTitle("Event Handlers Example");

      // Adding scene to the stage
      stage.setScene(scene);

      // Displaying the contents of the stage
      stage.show();
   }

   // Implementing the interface method
   public void animation() {
      launch();
   }
}

Server Program

RMI 服务器程序应该实现远程界面或扩展实现类。在这里,我们应该创建一个远程对象并将它绑定到 RMIregistry

以下是在这个应用程序中所用的服务器程序。在这里,我们将扩展上面所创建的类、创建一个远程对象并用 bind 名称 hello 将其注册到 RMI 注册表中。

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Server extends FxSample {
   public Server() {}
   public static void main(String args[]) {
      try {
         // Instantiating the implementation class
         FxSample obj = new FxSample();

         // Exporting the object of implementation class
         // (here we are exporting the remote object to the stub)
         Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);

         // Binding the remote object (stub) in the registry
         Registry registry = LocateRegistry.getRegistry();

         registry.bind("Hello", stub);
         System.err.println("Server ready");
      } catch (Exception e) {
         System.err.println("Server exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Client Program

以下是在这个应用程序中所用的客户端程序。在这里,我们将获取远程对象并调用其名称为 animation() 的方法。

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
   private Client() {}
   public static void main(String[] args) {
      try {
         // Getting the registry
         Registry registry = LocateRegistry.getRegistry(null);

         // Looking up the registry for the remote object
         Hello stub = (Hello) registry.lookup("Hello");

         // Calling the remote method using the obtained object
         stub.animation();

         System.out.println("Remote method invoked");
      } catch (Exception e) {
         System.err.println("Client exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Steps to Run the Example

以下是运行我们的 RMI 示例所需的步骤。

Step 1 − 打开已存储所有程序的文件夹,并按以下操作编译所有 Java 文件。

Javac *.java
stored programs

Step 2 − 使用以下命令启动 rmi 注册表。

start rmiregistry
start execution

这会在一个单独的窗口中启动一个 rmi 注册表,如下所示。

separate window

Step 3 − 按以下操作运行服务器类文件。

Java Server
run server

Step 4 - 如下所示运行客户端类文件。

java Client
client class

Verification - 在启动客户端后,您很快就会在服务器中看到以下输出。

event handler

Java RMI - Database Application

在上一章中,我们创建了一个示例 RMI 应用程序,其中客户端调用一个显示 GUI 窗口(JavaFX)的方法。

在本章中,我们将举例了解客户端程序如何检索驻留在服务器上的 MySQL 数据库中的一个表中的记录。

假设我们在数据库 details 中有一个表,名为 student_data 如下所示。

+----+--------+--------+------------+---------------------+
| ID | NAME   | BRANCH | PERCENTAGE | EMAIL               |
+----+--------+--------+------------+---------------------+
|  1 | Ram    | IT     |         85 | ram123@gmail.com    |
|  2 | Rahim  | EEE    |         95 | rahim123@gmail.com  |
|  3 | Robert | ECE    |         90 | robert123@gmail.com |
+----+--------+--------+------------+---------------------+

假设用户名为 myuser ,密码为 password

Creating a Student Class

使用 settergetter 方法创建一个 Student 类,如下所示。

public class Student implements java.io.Serializable {
   private int id, percent;
   private String name, branch, email;

   public int getId() {
      return id;
   }
   public String getName() {
      return name;
   }
   public String getBranch() {
      return branch;
   }
   public int getPercent() {
      return percent;
   }
   public String getEmail() {
      return email;
   }
   public void setID(int id) {
      this.id = id;
   }
   public void setName(String name) {
      this.name = name;
   }
   public void setBranch(String branch) {
      this.branch = branch;
   }
   public void setPercent(int percent) {
      this.percent = percent;
   }
   public void setEmail(String email) {
      this.email = email;
   }
}

Defining the Remote Interface

定义远程接口。此处,我们定义名为 Hello 的远程接口,其中有一个名为 getStudents () 的方法。此方法返回包含 Student 类对象的列表。

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.*;

// Creating Remote interface for our application
public interface Hello extends Remote {
   public List<Student> getStudents() throws Exception;
}

Developing the Implementation Class

创建一个类并实现上面创建的 interface.

此处,我们正在实现 Remote interfacegetStudents() 方法。当您调用此方法时,它会检索名为 student_data 的表的记录。使用其 setter 方法将这些值设置到 Student 类,将其添加到一个列表对象并返回该列表。

import java.sql.*;
import java.util.*;

// Implementing the remote interface
public class ImplExample implements Hello {

   // Implementing the interface method
   public List<Student> getStudents() throws Exception {
      List<Student> list = new ArrayList<Student>();

      // JDBC driver name and database URL
      String JDBC_DRIVER = "com.mysql.jdbc.Driver";
      String DB_URL = "jdbc:mysql://localhost:3306/details";

      // Database credentials
      String USER = "myuser";
      String PASS = "password";

      Connection conn = null;
      Statement stmt = null;

      //Register JDBC driver
      Class.forName("com.mysql.jdbc.Driver");

      //Open a connection
      System.out.println("Connecting to a selected database...");
      conn = DriverManager.getConnection(DB_URL, USER, PASS);
      System.out.println("Connected database successfully...");

      //Execute a query
      System.out.println("Creating statement...");

      stmt = conn.createStatement();
      String sql = "SELECT * FROM student_data";
      ResultSet rs = stmt.executeQuery(sql);

      //Extract data from result set
      while(rs.next()) {
         // Retrieve by column name
         int id  = rs.getInt("id");

         String name = rs.getString("name");
         String branch = rs.getString("branch");

         int percent = rs.getInt("percentage");
         String email = rs.getString("email");

         // Setting the values
         Student student = new Student();
         student.setID(id);
         student.setName(name);
         student.setBranch(branch);
         student.setPercent(percent);
         student.setEmail(email);
         list.add(student);
      }
      rs.close();
      return list;
   }
}

Server Program

RMI 服务器程序应实现远程接口或扩展实现类。此处,我们应创建一个远程对象并将其绑定到 RMI registry

以下是此应用程序的服务器程序。此处,我们将扩展上面创建的类,创建一个远程对象并使用绑定名称 hello 将其注册到 RMI 注册表中。

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Server extends ImplExample {
   public Server() {}
   public static void main(String args[]) {
      try {
         // Instantiating the implementation class
         ImplExample obj = new ImplExample();

         // Exporting the object of implementation class (
            here we are exporting the remote object to the stub)
         Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);

         // Binding the remote object (stub) in the registry
         Registry registry = LocateRegistry.getRegistry();

         registry.bind("Hello", stub);
         System.err.println("Server ready");
      } catch (Exception e) {
         System.err.println("Server exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Client Program

以下是此应用程序的客户端程序。此处,我们正在获取远程对象并调用名为 getStudents() 的方法。它从列表对象中检索表记录并显示它们。

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.*;

public class Client {
   private Client() {}
   public static void main(String[] args)throws Exception {
      try {
         // Getting the registry
         Registry registry = LocateRegistry.getRegistry(null);

         // Looking up the registry for the remote object
         Hello stub = (Hello) registry.lookup("Hello");

         // Calling the remote method using the obtained object
         List<Student> list = (List)stub.getStudents();
         for (Student s:list)v {

            // System.out.println("bc "+s.getBranch());
            System.out.println("ID: " + s.getId());
            System.out.println("name: " + s.getName());
            System.out.println("branch: " + s.getBranch());
            System.out.println("percent: " + s.getPercent());
            System.out.println("email: " + s.getEmail());
         }
      // System.out.println(list);
      } catch (Exception e) {
         System.err.println("Client exception: " + e.toString());
         e.printStackTrace();
      }
   }
}

Steps to Run the Example

以下是运行我们的 RMI 示例所需的步骤。

Step 1 − 打开已存储所有程序的文件夹,并按以下操作编译所有 Java 文件。

Javac *.java
stored programs

Step 2 − 使用以下命令启动 rmi 注册表。

start rmiregistry
start execution

这会在一个单独的窗口中启动一个 rmi 注册表,如下所示。

separate window

Step 3 − 按以下操作运行服务器类文件。

Java Server
run server

Step 4 - 如下所示运行客户端类文件。

java Client
client file