深入分析Java-RMI


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 原理

在這裏插入圖片描述
工作流程:

  1. When the client makes a call to the remote object, it is received by the stub which eventually passes this request to the RRL.
  2. 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.
  3. 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.
  4. 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章