一、RMI概述
java RMI(remote method invocation)即遠程方法調用,是允許運行在一個java虛擬機上的對象調用運行在另外一個java虛擬機上的對象的方法,JAVA RMI實現JAVA程序之間跨越JVM的遠程通信。通過RMI可以讓調用遠程JVM上對象方法,仿佛調用本地JVM上對象方法一樣簡單、快捷。
二、RMI框架

RMI主要有三個角色:RMI客戶端、RMI服務端、注冊表
RMI過程大體如下:
1.客戶端從RMI注冊表中查詢並獲取遠程對應引用。客戶端首先會與Stub進行交互,stub將遠程方法所需的參數進行序列化后,傳遞給遠程應用層RRL
2.stub和遠程對象具有相同的接口和方法列表,當客戶端調用遠程對象時,實際是有stub對象代理的。RRL將stub本地引用轉換為服務端上對象的遠程引用后,再將調用傳遞給傳輸層,傳輸層執行TCP發送
3.RMI服務端傳輸層監聽到請求后,將引用轉發給服務端的RRL。
4.服務端RRL將客戶端發送的遠程應用轉換為本地虛擬機引用后,傳遞給Skeleton。
5.Skeleton讀取參數,最后由服務端進行實際方法調用。
6.如果RMI客戶端調用存在返回值,則以此向下傳遞。
7.客戶端接收到返回值后,再以此向上傳遞。然后由stub反序列化返回值,最終傳遞給RMI客戶端
相關類關系如下:

三、RMI開發流程
1.RMI服務端
(1)遠程調用對象類:
1.1 定義一個繼承Remote接口的interface A,接口中的方法需要拋出RemoteException
1.2 遠程調用對象類需繼承UnicastRemoteObject類和interface A
(2)啟動注冊表並綁定對象
2.RMI客戶端
(1)定義用於接收遠程對象的Remote子接口,只需實現java.rmi.Remote接口即可。但要求必須與服務器端對等的Remote子接口保持一致,即有相同的接口名稱、包路徑和方法列表等
(2)通過注冊表查詢需要調用的對象,並強制轉換成客戶端定義的類
(3)進行方法調用
遠程調用對象類
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
public String welcome(String name) throws RemoteException;
}
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello{ public HelloImpl() throws RemoteException { } public String welcome(String name) throws RemoteException { return "Hello, " + name; } }
創建RMI服務端
import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws RemoteException { // 創建對象 Hello hello = new HelloImpl(); // 創建注冊表 Registry registry = LocateRegistry.createRegistry(10999); // 綁定對象到注冊表,並給他取名為hello registry.rebind("hello",hello); System.out.println("創建服務端成功!"); } }
創建RMI客戶端
import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws RemoteException, NotBoundException { // 獲取到注冊表的代理 Registry registry = LocateRegistry.getRegistry("localhost", 10999); // 利用注冊表的代理去查詢遠程注冊表中名為hello的對象 Hello hello = (Hello) registry.lookup("hello"); // 調用遠程方法 System.out.println(hello.welcome("tridentj")); } }
簡單本地運行的效果如下:


也可以通過Naming類創建,Naming相當於對Registry進行了封裝,部分Naming源代碼如下:


Naming封裝簡單示例:
import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class NamingServer { public static void main(String[] args) { try { HelloImpl hello = new HelloImpl(); //注冊RMI端口 LocateRegistry.createRegistry(10999); //綁定對象 Naming.bind("rmi://localhost:10999/hello",hello); System.out.println("啟動RMI服務成功!"); }catch (Exception e){ e.printStackTrace(); } } }
import java.rmi.Naming; public class NamingClient { public static void main(String[] args){ try { Hello hello = (Hello) Naming.lookup("rmi://localhost:10999/hello"); System.out.println(hello.welcome("tridentj")); }catch (Exception e){ e.printStackTrace(); } } }
運行效果如下:


四、JNDI
JNDI(JAVA Naming And Directory Interface)是JAVA命名和目錄接口。應用程序可以通過JNDI API去調用其他資源,包括JDBC、LDAP、RMI、DNS、NIS、windows注冊表等等。JNDI將不同的資源進行統一封裝,形成一套API,通過這套API,應用程序可以不用關心需要交互的資源細節,方便快捷的與資源進行交互,從而降低開發難度,提高效率。
使用JNDI創建服務對象
//配置JNDI工廠和JNDI的url
//使用Hashtable或Properties(繼承自Hashtable)
Properties pro = new Properties();
pro.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
pro.put(Context.PROVIDER_URL,"rmi://localhost:10999");
//創建初始化環境
Context ctx = new InitialContext(pro);
Context.INITIAL_CONTEXT_FACTORY指定JNDI具體處理的類名稱,例如RMI為com.sun.jndi.rmi.registry.RegistryContextFactory,LDAP為com.sun.jndi.ldap.LdapCtxFactory
JNDI-RMI遠程方法調用
RMI的服務工廠類為com.sun.jndi.rmi.registry.RegistryContextFactory
RMI服務端
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIService { public static void main(String[] args)throws Exception{ //創建rmi映射表 Registry registry = LocateRegistry.createRegistry(10999); Hello hello = new HelloImpl(); //對象板頂到注冊表 registry.bind("hello", hello); System.out.println("RMI服務啟動成功!"); } }
JNDI-RMI客戶端
import javax.naming.Context; import javax.naming.InitialContext; import java.util.Properties; public class JNDIRMIClient { public static void main(String[] args) throws Exception{ //配置JNDI工廠和JNDI的url Properties pro = new Properties(); pro.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory"); pro.put(Context.PROVIDER_URL,"rmi://localhost:10999"); //創建初始化環境 Context ctx = new InitialContext(pro); //jndi方式獲取遠程對象 Hello rHello = (Hello) ctx.lookup("rmi://localhost:10999/hello"); /** * 即便配置了context.PROVIDER_URL,當在lookup中修改成其他的url,程序以lookup中為准 * 相當於提前配置的PROVIDER_URL未起作用,故若lookup地址可控制,則存在風險點。 * */ //context.PROVIDER_URL已指定 //Hello rHello = (Hello) ctx.lookup("hello"); System.out.println(rHello.sayHello("tridentj")); } }
運行效果

