java RMI原理詳解


  java本身提供了一種RPC框架——RMI(即Remote Method Invoke 遠程方法調用),在編寫一個接口需要作為遠程調用時,都需要繼承了Remote,Remote 接口用於標識其方法可以從非本地虛擬機上調用的接口,只有在“遠程接口”(擴展 java.rmi.Remote 的接口)中指定的這些方法才可遠程使用,下面通過一個簡單的示例,來講解RMI原理以及開發流程:

  為了真正實現遠程調用,首先創建服務端工程rmi-server,結構如下:

  

  代碼說明:

  1.User.java:用於遠程調用時pojo對象的傳輸,該對象必須實現Serializable接口,否則在調用過程中,會拋出NotSerializableException異常,代碼如下:

/**
 * 用戶信息,用於遠程調用傳輸,必須實現Serializable接口
 * 
 * @author andy
 *
 */
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "name : " + this.name + ", age : " + this.age;
    }
}

   2.IHello.java:遠程接口,該接口需要繼承Remote接口,並且接口中的方法全都要拋出RemoteException異常,代碼如下:

import java.rmi.Remote;
import java.rmi.RemoteException;

import pers.andy.rmi.bean.User;

/**
 * 定義一個遠程接口,必須繼承Remote接口,其中需要遠程調用的方法必須拋出RemoteException異常
 * 
 * @author andy
 *
 */
public interface IHello extends Remote {

    /**
     * 更新user信息
     * @param user
     * @return
     * @throws RemoteException
     */
    public User updateUser(User user) throws RemoteException;
}

   3.HelloImpl:遠程接口實現類,必須繼承UnicastRemoteObject(繼承RemoteServer->繼承RemoteObject->實現Remote,Serializable),只有繼承UnicastRemoteObject類,才表明其可以作為遠程對象,被注冊到注冊表中供客戶端遠程調用(補充:客戶端lookup找到的對象,只是該遠程對象的Stub(存根對象),而服務端的對象有一個對應的骨架Skeleton(用於接收客戶端stub的請求,以及調用真實的對象)對應,Stub是遠程對象的客戶端代理,Skeleton是遠程對象的服務端代理,他們之間協作完成客戶端與服務器之間的方法調用時的通信。,代碼如下:

/**
 * 遠程的接口的實現,繼承了UnicastRemoteObject,表明該類作為一個遠程對象
 * 
 * @author andy
 *
 */
public class HelloImpl extends UnicastRemoteObject implements IHello {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * 因為UnicastRemoteObject的構造方法拋出了RemoteException異常,因此這里默認的構造方法必須寫,必須聲明拋出RemoteException異常
     * 
     * @throws RemoteException
     */
    public HelloImpl() throws RemoteException {
    }

    public User updateUser(User user) throws RemoteException {
        System.out.println("-------------- 客戶端發送的user為" + user.toString());
        user.setName("andy2");
        user.setAge(30);
        return user;
    }
}

  4.HelloServer:服務端啟動類,用於創建遠程對象注冊表以及注冊遠程對象,代碼如下:

/**
 * 服務端啟動類
 * 
 * @author andy
 *
 */
public class HelloServer {
    public static void main(String args[]) {
        try {
       // 本地主機上的遠程對象注冊表Registry的實例,並指定端口為8888,這一步必不可少(Java默認端口是1099)
            LocateRegistry.createRegistry(8888);
            // 把遠程對象注冊到RMI注冊服務器上,並命名為RHello
            // 綁定的URL標准格式為:rmi://host:port/name(其中協議名可以省略,下面兩種寫法都是正確的)
            Naming.bind("rmi://localhost:8888/RHello", rhello);
            // Naming.bind("//localhost:8888/RHello",rhello);
            System.out.println("------------遠程對象IHello注冊成功,等待客戶端調用...");
        } catch (RemoteException e) {
            System.out.println("創建遠程對象發生異常!");
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.out.println("發生重復綁定對象異常!");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("發生URL畸形異常!");
            e.printStackTrace();
        }
    }
}

  補充說明:為何HelloImpl繼承了UnicastRemoteObject就可以被作為遠程對象發布,查閱UnicastRemoteObject的源碼可以發現:

    protected UnicastRemoteObject() throws RemoteException
    {
        this(0);
    }
    protected UnicastRemoteObject(int port) throws RemoteException
    {
        this.port = port;
        exportObject((Remote) this, port);
    }

  其實在啟動server端的時候,new了HelloImpl對象,因為繼承了UnicastRemoteObject,會先調用父類的構造方法,這時候,就會將this(當前對象)通過exportObject方法注冊。

  所以,如果在被導出的對象需要繼承其它的類,那么就可以不采用集成UnicastRemoteObject的方式,而是通過exportObject方法將其導出為遠程對象:

...
// 創建一個遠程對象
IHello rhello = new HelloImpl(); //HelloImpl不需要繼承UnicastRemoteObject類,通過exportObject將其顯示導出 UnicastRemoteObject.exportObject(rhello,0); ...

  以上即是服務端所有代碼,接下來是創建客戶端工程,結構如下:

  

  實際應用開發中,客戶端的User.java和IHello.java應該是從服務端導出jar包的形式添加到依賴庫里,因此這邊只介紹HelloClient.java,該類為客戶端啟動類,用於在注冊表中查找遠程對象實現遠程方法調用,代碼如下:

/**
 * 客戶端啟動類
 * 
 * @author andy
 *
 */
public class HelloClient {
    public static void main(String args[]) {
        try {
            // 在RMI服務注冊表中查找名稱為RHello的對象,並調用其上的方法
            IHello rhello = (IHello) Naming.lookup("rmi://localhost:8888/RHello");
       // 構造user對象,測試遠程對象傳輸 User user = new User(); user.setAge(20); user.setName("andy"); System.out.println("-------------- 服務端返回的的user為" + rhello.updateUser(user).toString()); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }

   到此為止,客戶端和服務端的工程都搭建完畢,現在可以進行測試,執行次序和測試結果如下所示:

  1.首先運行服務端啟動類HelloServer,結果如下:

  服務端:------------遠程對象IHello注冊成功,等待客戶端調用...

  2.運行客戶端啟動類,結果如下:

  服務端:-------------- 客戶端發送的user為name : andy, age : 20

  客戶端:-------------- 服務端返回的的user為name : andy2, age : 30

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM