1 介紹
java本身的內部中是提供了一種RPC框架-RMI(即Remote Method Invoke,遠程方法調用),位於rt.jar包中,以java.rmi開頭的package中。
它是一種允許一個對象駐留在一個系統(JVM)來訪問/調用一個物體在另一個JVM中運行的機制
2 機制
2.1 內部體系架構
在一個RMI 的程序中,我們需要提供兩個模塊,一個是server模塊(用於提供服務),一個是client模塊(用於調度服務)
- server模塊中,我們需要使用註冊表來獲取本地主機的遠程註冊表實例,並將需要系統的遠程服務綁定到註冊表上去
- client模塊中,我們需要獲取服務器上的遠程對象,並視圖調用它
內部調用結構圖如下:
Transport Layer(傳輸層):傳輸層用於連接客戶端和服務斷,它用於管理現有的連接和設置新連接
Stud(存根):Stud位於client端,它作爲一個網關(gateway)用於代理遠程調度對象
Skeleton(骨架):Skeleton位於server端,它用於連接stud,用於接收調用server端的請求。
RRL(Remote Reference Layer)(遠程引用層):管理client和server端的引用
2.2 原理
工作流程:
- When the client makes a call to the remote object, it is received by the stub which eventually passes this request to the RRL.
- When the client-side RRL receives the request, it invokes a method called invoke() of the object remoteRef. It passes the request to the RRL on the server side.
- The RRL on the server side passes the request to the Skeleton (proxy on the server) which finally invokes the required object on the server.
- The result is passed all the way back to the client.
3 代碼示例
RMI在編寫接口作爲調度服務的時候,需要繼承自Remote,Remote用於標識其方法可以從非本地虛擬機上調用的接口,只有在遠程接口中指定這些方法纔可以遠程調用
並且由於網絡的原因,我們需要在聲明遠程調度的方法中主動拋出RemoteException異常
public interface Hello extends Remote{
String printMsg() throws RemoteException;
}
實現Hello接口,作爲服務提供
(注意,我們會繼承自UnicastRemoteObject 類,關於繼承這個類的底層原理,我們可以參考:https://blog.51cto.com/guojuanjun/1423392)
public class ImplExample extends UnicastRemoteObject implements Hello{
public ImplExample() throws RemoteException {
super();
}
@Override
public String printMsg() throws RemoteException {
System.out.println("This is an example RMI program");
return "ZHOUCG";
}
}
編寫 Server端,(在這裏我們通過命名空間Naming的方式發佈服務)
public class NamingService {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
// 本地主機上的遠程對象註冊表Registry的實例
LocateRegistry.createRegistry(1100);
// 創建一個遠程對象
Hello hello = new ImplExample();
// 把遠程對象註冊到RMI註冊服務器上,並命名爲Hello
// 綁定的URL標準格式爲:rmi://host:port/name
Naming.bind("rmi://localhost:1100/HelloNaming",hello);
System.out.println("======= 啓動RMI服務成功! =======");
}
}
編寫client端,調用服務:
public class NamingClient {
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
String remoteAddr = "rmi://localhost:1100/HelloNaming";
Hello hello = (Hello) Naming.lookup(remoteAddr);
String response = hello.printMsg();
System.out.println("=======> " + response + " <=======");
}
}
啓動Server和client,輸出結果:
Server端:
Client端:
上面的示例,是Java RMI的基本示例。
在spring源碼中,spring同樣提供了對於RMI代碼的封裝,我們可以很方便的在spring中使用RMI,代碼位於spring-context包下的org.springframework.remoting package 中
定義RMI服務端接口:
public interface Hello extends Remote{
String printMsg(String msg) throws RemoteException;
}
對於Server端的服務實現類:
@Service
public class ImpHello implements Hello {
public String printMsg(String msg) throws RemoteException {
System.out.println("RMI server get request msg:"+msg);
return "ZHOUCG"+msg;
}
}
定義Server端的服務配置:
@Configuration
public class RmiServiceConfig {
@Bean
public RmiServiceExporter registerService(Hello hello) {
RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
rmiServiceExporter.setServiceName("hello");
rmiServiceExporter.setService(hello);
rmiServiceExporter.setServiceInterface(Hello.class);
rmiServiceExporter.setRegistryPort(1101);
return rmiServiceExporter;
}
}
客戶端調用:
RMI客戶端服務配置:
@Configuration
public class RmiClientConfig {
@Bean
public Hello userInfo() {
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceUrl("rmi://localhost:1101/hello");
rmiProxyFactoryBean.setServiceInterface(Hello.class);
rmiProxyFactoryBean.afterPropertiesSet();
return (Hello) rmiProxyFactoryBean.getObject();
}
}
服務測試:
@RestController
@RequestMapping("/hello")
public class TestController {
@Autowired
private Hello hello;
@GetMapping
public String test() throws RemoteException {
String wl = hello.printMsg("WL");
return wl;
}
}
4 參考
https://www.tutorialspoint.com/java_rmi/java_rmi_application.htm