聲明:本案例使用的IDE是Eclipse,用戶端程式和伺服器程式位于同一個project中。
一、基本原理
RMI是Remote Method Invoke的縮寫,是JDK提供的一個完善的、簡單易用的遠端調用架構,它要求用戶端和伺服器端都是Java程式。下面簡述RMI的基本原理:如下圖所示,RMI采用代理來負責用戶端和伺服器之間socket通信的細節。RMI架構分别為遠端對象生成了用戶端代理和伺服器端代理,位于用戶端的代理稱為存根(Stub),位于伺服器端的代理稱為骨架(Skeleton)。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DOwUDO0kTNzETOxUDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
遠端對象會在用戶端生成存根對象。當用戶端調用遠端對象的方法時,實際上是調用本地存根的相應方法。然後,存根會把被通路的遠端對象名、方法名以及參數編組後發送給伺服器,由骨架去調用相應的遠端方法并把傳回值或異常傳回給用戶端。
二、基本步驟
一般來說,隻要繼承java.rmi.server.UnicastRemoteObject類和實作java.rmi.Remote 接口就可以成為遠端對象。由于java的單繼承,繼承了UnicastRemoteObject類之後就不能繼承其他的類,這時可以在構造方法中調用exportObect()方法,同樣可以将其導為遠端對象。實際上,UnicastRemoteObject的構造器也會去調用自身的exportObect()的靜态方法。 下面是建立一個RMI程式的基本步驟: (1)建立遠端接口,繼承java.rmi.Remote接口; (2)建立遠端類,實作遠端接口; (3)建立伺服器程式,在rmiregistry系統資料庫中注冊遠端對象; (4)建立用戶端程式,負責定位遠端對象,并且調用遠端方法。
*建立遠端接口
遠端接口中聲明了可以被用戶端通路的遠端方法,遠端接口應符合以下條件: (1)直接或間接繼承java.rmi.Remote接口; (2)接口中的所有方法聲明抛出java.rmi.RemoteException異常或父異常。
package main;
import java.rmi.Remote;
import java.rmi.RemoteException;
// Inherit the java.rmi.Remote interface
public interface HelloService extends Remote {
// Remote method should throw RemoteException
public String service(String data) throws RemoteException;
}
三、建立遠端類
遠端類應符合以下條件: (1)繼承java.rmi.server.UnicastRemoteObject類并實作遠端接口; (2)構造器必須抛出java.rmi.RemoteException異常。
package main;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// Inherit UnicastRemoteObject and implement HelloService interface
public class HelloServiceImpl extends UnicastRemoteObject
implements HelloService {
private static final long serialVersionUID = 1L;
private String name;
public HelloServiceImpl(String name) throws RemoteException {
super();
this.name = name;
// UnicastRemoteObject.exportObject(this, 0);
}
@Override
public String service(String data) throws RemoteException {
return data + name;
}
}
四、建立伺服器程式
在這裡要介紹幾個方法:
- bind(String name, Object obj): 注冊對象,把對象與服務名綁定。如果該服務名已與其他對象綁定,則會抛出NameAlreadyBoundException異常。
- rebind(String name, Object obj): 注冊對象,把對象與服務名綁定。如果該服務名已與其他對象綁定,不會抛異常,而是将新的對象綁定到該服務名上。
- lookup(String name): 查找對象,傳回與指定名稱相同的對象。
伺服器端首先要建立系統資料庫執行個體,然後将遠端對象注冊到系統資料庫上。伺服器程式運作就緒之後,不會立即結束,而是去監控用戶端的連接配接。關于服務名的命名格式,推薦使用“rmi://主機名:端口号/執行個體名”的方式,這樣可以避免在遠端對象很多的時候因服務名一緻而引發的沖突。預設的端口号1099可以省略。
package main;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Server {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
HelloService service1 = new HelloServiceImpl("service1");
Context namingContext = new InitialContext();
namingContext.rebind("rmi://localhost:1099/HelloService1",
service1);
}
catch (RemoteException | NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Successfully register a remote object.");
}
}
五、建立用戶端程式
package main;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
String url = "rmi://localhost:1099/";
try {
Context namingContext = new InitialContext();
HelloService serv = (HelloService) namingContext.lookup(
url + "HelloService1");
String data = "This is RMI Client.";
System.out.println(serv.service(data));
}
catch (NamingException | RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
用戶端先根據服務名查找遠端對象,然後才能調用遠端方法。是以,此處的服務名必須與在伺服器中注冊的服務名稱一緻,否則将無法找到遠端對象。
六、運作
在Eclipse中先啟動Server,然後啟動Client即可。