亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中的RMI使用方法詳解

 更新時間:2023年10月01日 09:20:13   作者:吳聲子夜歌  
這篇文章主要介紹了Java中的RMI使用方法,RMI是Java提供的一個完善的簡單易用的遠程方法調用框架,采用客戶服務器通信方式,在服務器上部署了提供各種服務的遠程對象,下面我們來詳細講解

1、概述

1.1、簡介

RMI 是 Java 提供的一個完善的簡單易用的遠程方法調用框架,采用客戶/服務器通信方式,在服務器上部署了提供各種服務的遠程對象,客戶端請求訪問服務器上遠程對象的方法,它要求客戶端與服務器端都是 Java 程序。

RMI 框架采用代理來負責客戶與遠程對象之間通過 Socket 進行通信的細節(jié)。RMI 框架為遠程對象分別生成了客戶端代理和服務器端代理。位于客戶端的代理必被稱為存根(Stub),位于服務器端的代理類被稱為骨架(Skeleton)。

1.2、原理

當客戶端調用遠程對象的一個方法時,實際上是調用本地存根對象的相應方法。存根對象與遠程對象具有同樣的接口。存根采用一種與平臺無關的編碼方式,把方法的參數(shù)編碼為字節(jié)序列,這個編碼過程被稱為參數(shù)編組。RMI 主要采用Java 序列化機制進行參數(shù)編組。

存根把以下請求信息發(fā)送給服務器:

  • 被訪問的遠程對象的名字
  • 被調用的方法的描述
  • 編組后的參數(shù)的字節(jié)序列

服務器端接收到客戶端的請求信息,然后由相應的骨架對象來處理這一請求信息,骨架對象執(zhí)行以下操作:

  • 反編組參數(shù),即把參數(shù)的字節(jié)序列反編碼為參數(shù)
  • 定位要訪問的遠程對象
  • 調用遠程對象的相應方法
  • 獲取方法調用產生的返回值或者異常,然后對它進行編組
  • 把編組后的返回值或者異常發(fā)送給客戶

客戶端的存根接收到服務器發(fā)送過來的編組后的返回值或者異常,再對它進行反編組,就得到調用遠程方法的返回結果

JDK5.0 之后,RMI 框架會在運行時自動為運程對象生成動態(tài)代理類(包括存根和骨架類),從而更徹底地封裝了 RMI 框架的實現(xiàn)細節(jié),簡化了 RMI 框架的使用方式。

RMI交互圖

Stub和Skeleton通信過程

方法調用從客戶對象-經(jīng)-存根(stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然后再次經(jīng)傳輸層,向上穿過遠程調用層和骨干網(wǎng)(Skeleton),到達服務器對象。

  • 存根:扮演著遠程服務器對象的代理的角色,使該對象可被客戶激活。
  • 遠程調用層:處理語義、管理單一或多重對象的通信,決定調用是應發(fā)往一個服務器還是多個。
  • 傳輸層:管理實際的連接,并且追蹤可以接受方法調用的遠程對象。
  • 骨干網(wǎng):完成對服務器對象實際的方法調用,并獲取返回值。返回值向下經(jīng)遠程引用層、服務器端的傳輸層傳遞回客戶端,再向上經(jīng)傳輸層和遠程調用層返回。最后,存根獲得返回值。

1.3、組成

RMI由3個部分構成:

第一個是rmiregistry(JDK提供的一個可以獨立運行的程序,在bin目錄下)

第二個是server端的程序,對外提供遠程對象

第三個是client端的程序,想要調用遠程對象的方法。

首先,先啟動rmiregistry服務,啟動時可以指定服務監(jiān)聽的端口,也可以使用默認的端口(1099)。其次,server端在本地先實例化一個提供服務的實現(xiàn)類,然后通過RMI提供的Naming/Context/Registry(下面實例用的Registry)等類的bind或rebind方法將剛才實例化好的實現(xiàn)類注冊到rmiregistry上并對外暴露一個名稱。最后,client端通過本地的接口和一個已知的名稱(即rmiregistry暴露出的名稱)再使用RMI提供的Naming/Context/Registry等類的lookup方法從RMIService那拿到實現(xiàn)類。這樣雖然本地沒有這個類的實現(xiàn)類,但所有的方法都在接口里了,便可以實現(xiàn)遠程調用對象的方法了。

1.4、數(shù)據(jù)傳遞

Java程序中引用類型(不包括基本類型)的參數(shù)傳遞是按引用傳遞的,對于在同一個虛擬機中的傳遞時是沒有問題的,因為的參數(shù)的引用對應的是同一個內存空間,在分布式系統(tǒng)中,由于對象不存在于同一個內存空間,虛擬機A的對象引用對于虛擬機B沒有任何意義,那么怎么解決這個問題呢?

第一種:

將引用傳遞更改為值傳遞,也就是將對象序列化為字節(jié),然后使用該字節(jié)的副本在客戶端和服務器之間傳遞,而且一個虛擬機中對該值的修改不會影響到其他主機中的數(shù)據(jù);但是對象的序列化也有一個問題,就是對象的嵌套引用就會造成序列化的嵌套,這必然會導致數(shù)據(jù)量的激增,因此我們需要有選擇進行序列化。

在Java中一個對象如果能夠被序列化,需要滿足下面兩個條件之一:

–是Java的基本類型;

–實現(xiàn)java.io.Serializable接口(String類即實現(xiàn)了該接口);

對于容器類,如果其中的對象是可以序列化的,那么該容器也是可以序列化的;

可序列化的子類也是可以序列化的;

第二種:

使用引用傳遞,每當遠程主機調用本地主機方法時,該調用還要通過本地主機查詢該引用對應的對象,在任何一臺機器上的改變都會影響原始主機上的數(shù)據(jù),因為這個對象是共享的;

RMI中的參數(shù)傳遞和結果返回可以使用的三種機制(取決于數(shù)據(jù)類型):

簡單類型:按值傳遞,直接傳遞數(shù)據(jù)拷貝;?

遠程對象引用(實現(xiàn)了Remote接口):以遠程對象的引用傳遞;?

遠程對象引用(未實現(xiàn)Remote接口):按值傳遞,通過序列化對象傳遞副本,本身不允許序列化的對象不允許傳遞給遠程方法;

在調用遠程對象的方法之前需要一個遠程對象的引用,如何獲得這個遠程對象的引用在RMI中是一個關鍵的問題,如果將遠程對象的發(fā)現(xiàn)類比于IP地址的發(fā)現(xiàn)可能比較好理解一些。

平常我們上網(wǎng)是通過域名來定位一個網(wǎng)站,實際上網(wǎng)絡是通過IP地址來定位網(wǎng)站,因此其中就存在一個映射的過程,域名系統(tǒng)(DNS)就是為了這個目的出現(xiàn)的,在域名系統(tǒng)中通過域名來查找對應的IP地址來訪問對應的服務器。

對應的,IP地址在這里就相當于遠程對象的引用,而DNS則相當于一個注冊表(Registry)

而域名在RMI中就相當于遠程對象的標識符,客戶端通過提供遠程對象的標識符訪問注冊表,來得到遠程對象的引用。這個標識符是類似URL地址格式的,它要滿足的規(guī)范如下:

該名稱是URL形式的,類似于http的URL,schema是rmi;

格式類似于rmi://host:port/name,host指明注冊表運行的注解,port表明接收調用的端口,name是一個標識該對象的簡單名稱

主機和端口都是可選的,如果省略主機,則默認運行在本地;如果端口也省略,則默認端口是1099;

2、示例

2.1、創(chuàng)建接口

創(chuàng)建一個接口Hello,該接口需要繼承Remote接口,接口所定義的方法需要拋出RemoteException異常:

//遠程接口,該接口需要繼承Remote接口,并且接口中的方法全都要拋出RemoteException異常
public interface Hello extends Remote {
    public String welcome(String name) throws RemoteException;
}

2.2、實現(xiàn)接口類

基于上面定義的接口實現(xiàn)一個類Helloimpl,該實現(xiàn)類需要繼承UnicastRemoteObject類,同樣重載的方法需要拋出RemoteException異常:

/**
 * 遠程接口實現(xiàn)類,必須繼承UnicastRemoteObject
 * (繼承RemoteServer->繼承RemoteObject->實現(xiàn)Remote,Serializable),
 *  只有繼承UnicastRemoteObject類,才表明其可以作為遠程對象,被注冊到注冊表中供客戶端遠程調用
 * (補充:客戶端lookup找到的對象,只是該遠程對象的Stub(存根對象),
 *  而服務端的對象有一個對應的骨架Skeleton(用于接收客戶端stub的請求,以及調用真實的對象)對應,
 *  Stub是遠程對象的客戶端代理,Skeleton是遠程對象的服務端代理,
 *  他們之間協(xié)作完成客戶端與服務器之間的方法調用時的通信。)
 */
public class HelloImpl extends UnicastRemoteObject implements Hello {
    //因為UnicastRemoteObject的構造方法拋出了RemoteException異常,
    //因此這里默認的構造方法必須寫,也必須聲明拋出RemoteException異常
    public HelloImpl() throws RemoteException {
    }
    @Override
    public String welcome(String name) throws RemoteException {
        return "Hello " + name;
    }
}

如果一個遠程類已經(jīng)繼承了其他類,無法再繼承 UnicastRemoteObiect 類,那么可以在構造方法中調用 UnicastRemoteObject 類的靜態(tài) expotObject 方法,同樣,遠程類的構造方法也必須聲明拋出 RemoteException

public class HelloImpl2 implements Hello {
    @Override
    public String welcome(String name) throws RemoteException {
        return "Hello " + name;
    }
    public HelloImpl2() throws RemoteException{
        //參數(shù) port 指定監(jiān)聽的端口,如果取值為0,就表示監(jiān)聽任意一個匿名端口
        UnicastRemoteObject.exportObject(this, 0);
    }
}

2.3、創(chuàng)建服務端

服務端創(chuàng)建了一個注冊表,并注冊了客戶端需要的對象:

public class Server {
    public static void main(String[] args) throws RemoteException {
        //創(chuàng)建對象
        Hello hello = new HelloImpl();
        // 本地主機上的遠程對象注冊表Registry的實例,
        // 并指定端口,這一步必不可少(Java默認端口是1099)
        Registry registry = LocateRegistry.createRegistry(1099);
        //綁定對象到注冊表,并給它取名為hello
        registry.rebind("hello", hello);
    }
}

向注冊器注冊遠程對象有三種方式:

//創(chuàng)建遠程對象
HelloService service1 = new HelloServiceImpl("service1");
//方式1:調用 java.i.registry.Registy 接口的 bind 或 rebind 方法
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("HelloService1", service1);
//方式2:調用命名服務類 java.rmi.Naming 的 bind 或 rebind 方法
Naming.rebind("HelloService1", service1);
//方式3:調用 JNDI API 的 javax.naming.Context 接口的 bind 或rebind 方法
Context namingContext = new InitialContext();
namingContext.rebind("rmi:HelloService1", service1);

2.4、客戶端調用遠程對象

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        //獲取到注冊表的代理
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        //利用注冊表的代理去查詢遠程注冊表中名為hello的對象
        Hello hello = (Hello) registry.lookup("hello");
        //調用遠程方法
        System.out.println(hello.welcome("tom"));
    }
}

2.5、運行結果

先運行服務端,再運行客戶端:

3、其它

3.1、遠程方法中的參數(shù)與返回值傳遞

當客戶端調用服務器端的遠程對象的方法時,客戶端會向服務器端傳遞參數(shù),服務器端則會向客戶端傳遞返回值。RMI 規(guī)范對參數(shù)以及返回值的傳遞的規(guī)定如下所述:

  • 只有基本類型的數(shù)據(jù)、遠程對象以及可序列化的對象才可以被作為參數(shù)或者返回值進行傳遞
  • 如果參數(shù)或返回值是一個遠程對象,那么把它的存根對象傳遞到接收方。也就是說接收方得到的是遠程對象的存根對象
  • 如果參數(shù)或返回值是可序列化對象,那么直接傳遞該對象的序列化數(shù)據(jù)。也就是說接收方得到的是發(fā)送方的可序列化對象的復制品
  • 如果參數(shù)或返回值是基本類型的數(shù)據(jù),那么直接傳遞該數(shù)據(jù)的序列化數(shù)據(jù)。也就是說,接收方得到的是發(fā)送方的基本類型的數(shù)據(jù)的復制品

3.2、分布式垃圾收集

在 Java 虛擬機中,對于一個本地對象,只要不被本地 Java 虛擬機內的任何變量引用,它就會結束生命周期,可以被垃圾回收器回收。而對于一個遠程對象,不僅會被本地 Java 虛擬機內的變量引用,還會被遠程引用

服務器端的一個遠程對象受到三種引用:

  • 服務器端的一個本地對象持有它的本地引用
  • 這個遠程對象已經(jīng)被注冊到 RMI 注冊器,可以理解為,RMI 注冊器持有它的引用
  • 客戶端獲得了這個遠程對象的存根對象,可以理解為,客戶端持有它的遠程引用

RMI 框架采用分布式垃圾收集機制來管理遠程對象的生命周期,當一個遠程對象不受到任何本地引用和遠程引用時,這個遠程對象才會結束生命周期,并且可以被本地 Java 虛擬機的垃圾回收器回收。

服務器端如何知道客戶端持有一個遠程對象的遠程引用呢?當客戶端獲得了一個服務器端的遠程對象的存根后,就會向服務器發(fā)送一條租約通知,告訴服務器自己持有這個遠程對象的引用了??蛻舳藢@個遠程對象有一個租約期限,默認值為 600000ms。當至達了租約期限的一半時間,客戶如果還持有遠程引用,就會再次向服務器發(fā)送租約通知??蛻舳瞬粩嘣诮o定的時間間隔中向服務器發(fā)送租約通知,從而使腸務器知道客戶端一直持有遠程對象的引用。如果在租約到期后,服務器端沒有繼續(xù)收到客戶端的新的租約通知,服務器端就會認為這個客戶已經(jīng)不再持有遠程對象的引用了

3.3、動態(tài)加載

遠程對象一般分布在服務器端,當客戶端試圖調用遠程對象的方法時,如果在客戶端還不存在遠程對象所依賴的類文件,比如遠程方法的參數(shù)和返回值對應的類文件,客戶就會從 java.rmi.server.codebase 系統(tǒng)屬性指定的位貿動態(tài)加載該類文件

同樣,當服務器端訪問客戶端的遠程對象時,如果服務器端不存在相關的類文件,腐務器就會從 java.rmi.server.codebase 屬性指定的位置動態(tài)加載它們

此外,當服務器向 RMI 注冊器注冊遠程對象時,注冊器也會從 java.rmi.server.codebase 屬性指定的位置動態(tài)加載相關的遠程接口的類文件

前面的例子都是在同一個 classpath 下運行服務器程序以及客戶程序的,這些程序都能從本地 classpath 中找到相應的類文件,因此無須從 java.rmi.server.codebase 屬性指定的位置動態(tài)加載類。而在實際應用中,客戶程序與服務器程序運行在不同的主機上,因此當客戶端調用服務器端的遠程對象的方法時,有可能需要從遠程文件系統(tǒng)加載類文件。同樣,當服務器端調用客戶端的遠程對象的方法時,也有可能從遠程文件系統(tǒng)加載類文件

我們可以且把這些需要被加載的類的文件都集中放在網(wǎng)絡上的同一地方,啟動時將java.rmi.server.codebase 設置為指定位置,從而實現(xiàn)動態(tài)加載

start java -Djava.rmi.server.codebase=http://www.javathinker.net/download/

到此這篇關于Java中的RMI使用方法詳解的文章就介紹到這了,更多相關Java RMI使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • JPA延遲加載no Session報錯解決分析

    JPA延遲加載no Session報錯解決分析

    這篇文章主要為大家介紹了JPA延遲加載no Session報錯解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • gson ajax 數(shù)字精度丟失問題的解決方法

    gson ajax 數(shù)字精度丟失問題的解決方法

    下面小編就為大家?guī)硪黄猤son ajax 數(shù)字精度丟失問題的解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • 一篇文章教你將JAVA的RabbitMQz與SpringBoot整合

    一篇文章教你將JAVA的RabbitMQz與SpringBoot整合

    這篇文章主要介紹了如何將JAVA的RabbitMQz與SpringBoot整合,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-09-09
  • 詳解MyBatis XML配置解析

    詳解MyBatis XML配置解析

    這篇文章主要介紹了詳解MyBatis XML配置解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • SpringSecurity多表多端賬戶登錄的實現(xiàn)

    SpringSecurity多表多端賬戶登錄的實現(xiàn)

    本文主要介紹了SpringSecurity多表多端賬戶登錄的實現(xiàn)
    2024-05-05
  • 通過Spring層面進行事務回滾的實現(xiàn)

    通過Spring層面進行事務回滾的實現(xiàn)

    本文主要介紹了通過Spring層面進行事務回滾的實現(xiàn),包括聲明式事務和編程式事務,具有一定的參考價值,感興趣的可以了解一下
    2025-04-04
  • IDEA搭建SpringBoot多模塊聚合工程過程詳解(多模塊聚合工程)

    IDEA搭建SpringBoot多模塊聚合工程過程詳解(多模塊聚合工程)

    這篇文章主要是介紹一下,如何在IDEA開發(fā)工具下,搭建一個基于SpringBoot的多模塊聚合工程項目,本篇文章,將項目模塊細分為幾個子工程模塊,在文中給大家詳細介紹過,對IDEA搭建SpringBoot多模塊聚合工程感興趣的朋友一起看看吧
    2022-04-04
  • 有關ServletConfig與ServletContext的訪問

    有關ServletConfig與ServletContext的訪問

    下面小編就為大家?guī)硪黄嘘PServletConfig與ServletContext的訪問。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • Java繼承子類的構造函數(shù)方式

    Java繼承子類的構造函數(shù)方式

    這篇文章主要介紹了Java繼承子類的構造函數(shù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • IntelliJ IDEA彈出“IntelliJ IDEA License Activation”的處理方法

    IntelliJ IDEA彈出“IntelliJ IDEA License Activation”的處理方法

    這篇文章主要介紹了IntelliJ IDEA彈出“IntelliJ IDEA License Activation”的處理方法,本文給出解決方法,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09

最新評論