Java 遠程方法調用,即 Java RMI( Java Remote Method Invocation ) 。顧名思義,可以使客戶機上運行的程序能夠調用遠程服務器上的對象(方法)。
下面主要介紹一下使用步驟:
1.定義遠程接口(服務端)
遠程接口定義出可以讓客戶遠程調用的方法。
此接口必須實現 java.rmi.Remote
接口,來表示其支持遠程調用;同時其中聲明的所有方法,需要拋出RemoteException
異常,因為遠程調用的不穩定性(如網絡原因等),這樣可以讓客戶端在調用失敗時進行相應的處理。
public interface DemoService extends Remote { String sayHello() throws RemoteException; }
2.實現遠程接口(服務端)
遠程接口的實現類如果想要被遠程訪問,可以有如下實現方式:
繼承java.rmi.server.UnicastRemoteObject
類
public class DemoServerImpl extends UnicastRemoteObject implements DemoService{ public DemoServerImpl() throws RemoteException { // 因為 UnicastRemoteObject 構造器拋出 RemoteException // 所以此處只能聲明一個構造器並拋出對應異常 } @Override public String sayHello() throws RemoteException { return "Hello World"; } }
如果不想繼承UnicastRemoteObject
類,則需要使用 UnicastRemoteObject
類的靜態方法exportObject(Remote obj, int port)
將對象導出
其中如果端口設為 0 的話,則表示任何合適的端口都可用來監聽客戶連接
public class DemoServerImpl implements DemoService{ public DemoServerImpl() throws RemoteException { UnicastRemoteObject.exportObject(this, 0); } @Override public String sayHello() throws RemoteException { return "Hello World"; } }
這兩者方法本質上是一樣的,在UnicaseRemoteObject
類的構造方法中,其實也是調用了exportObject
方法
// UnicaseRemoteObject中的部分源碼 protected UnicastRemoteObject() throws RemoteException { this(0); } // if port is zero, an anonymous port is chosen protected UnicastRemoteObject(int port) throws RemoteException { this.port = port; exportObject((Remote) this, port); }
3.啟動 RMI 注冊表
注冊表就像一個電話簿,啟動后即可將提供的服務注冊到其中,客戶可以通過它查詢到服務來進行調用
啟動注冊表有兩種方法,一種是通過命令行rmiregistry
來啟動,另一種方式是通過LocateRegistry.createRegistry(int port)
方法。
4.注冊開啟遠程服務
注冊服務共有三種方式:
-
LocateRegistry 類的對象的 rebind() 和 lookup() 來實現綁定注冊和查找遠程對象的
-
利用命名服務 java.rmi.Naming 類的 rebind() 和 lookup() 來實現綁定注冊和查找遠程對象的
-
利用JNDI(Java Naming and Directory Interface,Java命名和目錄接口) java.naming.InitialContext 類來 rebind() 和 lookup() 來實現綁定注冊和查找遠程對象的
其中第二種方式實際是對第一種方式的簡單封裝,在內部仍是調用Registry
類的bind
方法
// Naming 類的部分源碼 (為了節省篇幅,去除了拋出異常部分) public static void bind(String name, Remote obj) throws ... { ParsedNamingURL parsed = parseURL(name); Registry registry = getRegistry(parsed); if (obj == null) throw new NullPointerException("cannot bind to null"); registry.bind(parsed.name, obj); }
服務測試類:
public class ServerTest { public static void main(String[] args) throws Exception{ String name = "rmi.service.DemoService"; // 創建服務 DemoService service = new DemoServerImpl(); // 創建本機 1099 端口上的 RMI 注冊表 Registry registry1 = LocateRegistry.createRegistry(1099); /***************** 以下為注冊方法一 ************/ // 將服務綁定到注冊表中 registry1.bind(name, service); /***************** 以下為注冊方法二 ************/ // Naming.bind(name, service); /***************** 以下為注冊方法三 ************/ //Context namingContext = new InitialContext(); //namingContext.bind("rmi:" + name, service); // 此方式 name 需要以 rmi: 開頭 } }
客戶端測試類:
public class ClientTest { public static void main(String[] args) throws Exception { String name = "rmi.service.DemoService"; /***************** 以下為查找服務方法一 ************/ // 獲取注冊表 Registry registry = LocateRegistry.getRegistry("localhost", 1099); // 查找對應的服務 DemoService service = (DemoService) registry.lookup(name); /***************** 以下為查找服務方法二 ************/ // DemoService service = (DemoService) Naming.lookup(name); /***************** 以下為查找服務方法三 ************/ //Context namingContext = new InitialContext(); //DemoService service = (DemoService) namingContext.lookup("rmi:" + name); // 調用服務 System.out.println(service.sayHello()); } }
參考文章:https://segmentfault.com/a/1190000004494341