1. 程式人生 > >java反射機制(2)- 實踐:反射機制+動態代理實現模擬RMI遠端方法呼叫

java反射機制(2)- 實踐:反射機制+動態代理實現模擬RMI遠端方法呼叫

1 涉及主要知識點

  1、RMI(Remote Method Invocation):遠端方法呼叫是一種計算機之間利用遠端物件互相呼叫實現雙方通訊的一種通訊機制。使用這種機制,某一臺計算機上的物件可以呼叫另外 一臺計算機上的物件來獲取遠端資料。RMI是Enterprise JavaBeans的支柱,是建立分散式Java應用程式的方便途徑。RMI的基本框架如下圖所示:

      這裡寫圖片描述
本案例中的遠端代理的基本模型如下:
這裡寫圖片描述

2 專案詳解

  專案需求:客戶端LocalClient請求呼叫遠端伺服器端RemoteServer的業務類RemoteService的方法getService,並將方法結果返回。其中將客戶端請求方法的資訊封裝成Call物件,再將方法的返回結果設定進call物件中,即B/S之間傳遞call物件,因此Call物件需要實現Serializable介面。底層Socket通訊由Connector負責。由於涉及到遠端代理,這裡採用JDK動態代理模式。採用工廠模式,根據給定的伺服器的host和port,以及類名,由工廠RemoteServiceProxyFactory獲取代理類。代理模式中,代理類和目標類需要實現共同的介面Service,客戶端針對Service介面程式設計。
  以上過程基本描述了所涉及到了類名,系統類結構圖如下:


這裡寫圖片描述
  (1)建立代理類和目標類需要實現共同的介面Service。

package com.markliu.remote.service;
/**
 * Service介面。代理類和被代理類抖需要實現該介面
 */
public interface Service {
    public String getService(String name, int number);
}

  (2)伺服器端建立RemoteService類,實現了Service 介面。

package com.markliu.remote.serviceimpl;
import com.markliu.remote.service.Service;
/**
 * 伺服器端目標業務類,被代理物件
 */
public class RemoteService implements Service { @Override public String getService(String name, int number) { return name + ":" + number; } }

  (3)建立封裝客戶端請求和返回結果資訊的Call類。
  為了便於按照面向物件的方式來處理客戶端與伺服器端的通訊,可以把它們傳送的資訊用 Call 類來表示。一個 Call 物件表示客戶端發起的一個遠端呼叫,它包括呼叫的類名或介面名、方法名、方法引數型別、方法引數值和方法執行結果。

package com.markliu.local.bean;
import java.io.Serializable;
/**
 * 請求的javabean
 */
public class Call implements Serializable{
    private static final long serialVersionUID = 5386052199960133937L;
    private String className; // 呼叫的類名或介面名
    private String methodName; // 呼叫的方法名
    private Class<?>[] paramTypes; // 方法引數型別
    private Object[] params; // 呼叫方法時傳入的引數值
    /**
     * 表示方法的執行結果 如果方法正常執行,則 result 為方法返回值,
     * 如果方法丟擲異常,那麼 result 為該異常。
     */
    private Object result;
    public Call() {}
    public Call(String className, String methodName, Class<?>[] paramTypes, Object[] params) {
        this.className = className;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }
    // 省略了get和set方法
}

  (4)建立動態代理模式中實際的業務處理類,實現了InvocationHandler 介面。

package com.markliu.local.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.markliu.local.bean.Call;

public class ServiceInvocationHandler implements InvocationHandler {

    private Class<?> classType;
    private String host;
    private Integer port;

    public Class<?> getClassType() {
        return classType;
    }
    public ServiceInvocationHandler(Class<?> classType, String host, Integer port) {
        this.classType = classType;
        this.host = host;
        this.port = port;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 封裝請求資訊
        Call call = new Call(classType.getName(), method.getName(), method.getParameterTypes(), args);
        // 建立連結
        Connector connector = new Connector();
        connector.connect(host, port);
        // 傳送請求
        connector.sendCall(call);
        // 獲取封裝遠端方法呼叫結果的物件
        connector.close();
        Object returnResult = call.getResult();
        return returnResult;
    }
}

  (5)建立獲取代理類的工廠RemoteServiceProxyFactory。

package com.markliu.local.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * 動態建立RemoteService代理類的工廠
 */
public class RemoteServiceProxyFactory {

    public static Object getRemoteServiceProxy(InvocationHandler h) {
        Class<?> classType = ((ServiceInvocationHandler) h).getClassType();
        // 獲取動態代理類
        Object proxy = Proxy.newProxyInstance(classType.getClassLoader(), 
                new Class[]{classType}, h);
        return proxy;
    }
}

  (6)建立底層Socket通訊的Connector類,負責建立攔截、傳送和接受Call物件。

package com.markliu.local.service;
// 省略import 

/**
 * 負責建立連結
 */
public class Connector {
    private Socket linksocket;
    private InputStream in;
    private ObjectInputStream objIn;
    private OutputStream out;
    private ObjectOutputStream objOut;

    public Connector(){}
    /**
     * 建立連結
     */
    public void connect(String host, Integer port) throws UnknownHostException, IOException {
        linksocket = new Socket(host, port);
        in = linksocket.getInputStream();
        out = linksocket.getOutputStream();
        objOut = new ObjectOutputStream(out);
        objIn = new ObjectInputStream(in);
    }
    /**
     * 傳送請求call物件
     */
    public void sendCall(Call call) throws IOException {
        objOut.writeObject(call);
    }
    /**
     * 獲取請求物件
     */
    public Call receive() throws ClassNotFoundException, IOException {
        return (Call) objIn.readObject();
    }
    /**
     * 簡單處理關閉連結
     */
    public void close() {
        try {
            linksocket.close();
            objIn.close();
            objOut.close();
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  (7)建立遠端伺服器。

package com.markliu.remote.main;
// 省略import 

public class RemoteServer {

    private Service remoteService;
    public RemoteServer() {
        remoteService  = new RemoteService();
    }
    public static void main(String[] args) throws Exception {
        RemoteServer server = new RemoteServer();
        System.out.println("遠端伺服器啟動......DONE!");
        server.service();
    }

    public void service() throws Exception {
        @SuppressWarnings("resource")
        ServerSocket serverSocket = new ServerSocket(8001);
        while (true) {
                Socket socket = serverSocket.accept();
                InputStream in = socket.getInputStream();
                ObjectInputStream objIn = new ObjectInputStream(in);
                OutputStream out = socket.getOutputStream();
                ObjectOutputStream objOut = new ObjectOutputStream(out);
                // 物件輸入流讀取請求的call物件
                Call call = (Call) objIn.readObject();
                System.out.println("客戶端傳送的請求物件:" + call);
                call = getCallResult(call);
                // 傳送處理的結果回客戶端
                objOut.writeObject(call);
                objIn.close();
                in.close();
                objOut.close();
                out.close();
                socket.close();
        }
    }

    /**
     * 通過反射機制呼叫call中指定的類的方法,並將返回結果設定到原call物件中
     */
    private Call getCallResult(Call call) throws Exception {
        String className = call.getClassName();
        String methodName = call.getMethodName();
        Object[] params = call.getParams();
        Class<?>[] paramsTypes = call.getParamTypes();

        Class<?> classType = Class.forName(className);
        // 獲取所要呼叫的方法
        Method method = classType.getMethod(methodName, paramsTypes);
        Object result = method.invoke(remoteService, params);
        call.setResult(result);
        return call;
    }
}

  (8)建立本地客戶端。

package com.markliu.local.main;
import java.lang.reflect.InvocationHandler;
import com.markliu.local.service.RemoteServiceProxyFactory;
import com.markliu.local.service.ServiceInvocationHandler;
import com.markliu.remote.service.Service;

public class LocalClient {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        Integer port = 8001;
        Class<?> classType = com.markliu.remote.service.Service.class;
        InvocationHandler h = new ServiceInvocationHandler(classType, host, port);
        Service serviceProxy = (Service) RemoteServiceProxyFactory.getRemoteServiceProxy(h);
        String result = serviceProxy.getService("SunnyMarkLiu", 22);
        System.out.println("呼叫遠端方法getService的結果:" + result);
    }
}

  LocalClient 呼叫 RemoteServer 端的RemoteService物件的 getService(name,age)方法的執行結果如下:
        這裡寫圖片描述

3 發現的問題

  Socket同時使用ObjectInputStream和ObjectOutputStream傳輸序列化物件時的順序問題:
  在網路通訊中,主機與客戶端若使用ObjectInputStream與ObjectOutputStream建立物件通訊,必須注重宣告此兩個物件的順序。
  伺服器端先建立ObjectInputStream後建立ObjectOutputStream,則對應地客戶端要先建立ObjectOutputStream後建立ObjectInputStream,否則會造成兩方互相等待資料而導致死鎖。
  原因是建立ObjectInputStream物件時需要先接收一定的header資料,接收到這些資料之前會處於阻塞狀態。

  public ObjectInputStream(InputStream in) throws IOException的官方API顯示:
Creates an ObjectInputStream that reads from the specified InputStream. A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header.
  在建立ObjectInputStream物件時會檢查ObjectOutputStream所傳過來了頭資訊,如果沒有資訊將一直會阻塞。
  正確的做法:
  Server:

    ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());  
    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());  

  Client:

    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());  
    ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());  

4 極客祕籍

  現在我們有一個有趣的問題,就是客戶端在建立動態代理類時候,需要有伺服器端的Service介面和RemoteService類,否則客戶端在呼叫Proxy.newProxyInstance方法是會報找不到類型別和介面等錯誤。本案例中簡單的採用將這些類複製一份到客戶端。
  還有一種不錯的方式-動態類下載。以上這些類可以“貼上”URL,告訴客戶端RMI系統去尋找需要的類檔案。建立動態代理類的時候,如果本地沒有發現這些類,則從根據URL下載類檔案。所以需要一個簡單的Web伺服器來提供這些類檔案,也需要提供客戶端的安全引數等。

相關推薦

java反射機制2- 實踐反射機制+動態代理實現模擬RMI遠端方法呼叫

1 涉及主要知識點   1、RMI(Remote Method Invocation):遠端方法呼叫是一種計算機之間利用遠端物件互相呼叫實現雙方通訊的一種通訊機制。使用這種機制,某一臺計算機上

「深入Java虛擬機2Class類文件結構

1.5 trac 三種 type 類構造 face 方法 class throw Java是與平臺無關的語言,這得益於Java源代碼編譯後生成的存儲字節碼的文件,即Class文件,以及Java虛擬機的實現。不僅使用Java編譯器可以把Java代碼編譯成存儲字節碼的Class

從零開始理解JAVA事件處理機制2

extend nds 接下來 htm ref param 簡單 tostring ansi 第一節中的示例過於簡單《從零開始理解JAVA事件處理機制(1)》,簡單到讓大家覺得這樣的代碼簡直毫無用處。但是沒辦法,我們要繼續寫這毫無用處的代碼,然後引出下一階段真正有益的代碼。

「深入Java虛擬機4類加載機制

來講 合並 field 數字 對象 例如 二進制 種類 jar 類加載過程 類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。 其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段

Java語法糖2自動裝箱和自動拆箱

eth 空指針 lang 指針 反編譯 class path load pointer 自動拆箱和自動裝箱 Java為每種基本數據類型都提供了對應的包裝器類型。舉個例子: public class TestMain{public static void main(Strin

Java併發程式設計2執行緒中斷含程式碼

使用interrupt()中斷執行緒當一個執行緒執行時,另一個執行緒可以呼叫對應的Thread物件的interrupt()方法來中斷它,該方法只是在目標執行緒中設定一個標誌,表示它已經被中斷,並立即返回。這裡需要注意的是,如果只是單純的呼叫interrupt()方法,執行緒並沒有實際被中斷,會繼續往下執行。

Java的異常捕捉機制2

 1)編寫一個Java程式,接收使用者通過鍵盤不斷輸入表示某門課程的成績的字串(按回車為一個字串結束),當輸入非法數字(輸入值小於0或大於100)時提示成績輸入有誤,當輸入為非數字的字串時提示輸入格式不合法。 程式原始碼: 1.事先需要自定義異常類如下:

【深入Java虛擬機器5多型性實現機制—靜態分派與動態分派

方法解析 Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在Class檔案裡面儲存的都只是符號引用,而不是方法在實際執行時記憶體佈局中的入口地址。這個特性給Java帶來了更強大的動態擴充套件能力,使得可以在類執行期間才能確定某些目標方法的直接引

Java網路程式設計2TCP和UDP

1、多執行緒“服務端-客戶端”   TCP客戶端使用Socket來連線伺服器和與伺服器通訊。以下為在主執行緒中將使用者輸入傳送給服務端,在建立的執行緒中將服務端發回的資料輸出來: import java.net.*; import java.io.*; class Cl

JAVA設計模式2抽象工廠模式

抽象工廠模式是一個超級工廠,用來建立其他工廠。 這個工廠也被稱為工廠的工廠。 這種型別的設計模式屬於建立模式,因為此模式提供了建立物件的最佳方法之一。在抽象工廠模式中,介面負責建立相關物件的工廠,而不明確指定它們的類。 每個生成的工廠可以按照工廠模式提供物件。 實現例項 我們將建立一個Sha

JVM十虛擬機器類載入機制2

1. 類載入器作用           類載入器雖然只用於實現類的載入動作,但它在Java程式起到的作用卻遠大於類載入階段。對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在Java虛擬機器中

python爬蟲入門八多程序/多執行緒 python佇列Queue Python多執行緒(2)——執行緒同步機制 python學習筆記——多程序中共享記憶體Value & Array python 之 多程序 Python多程序 Python 使用multiprocessing 特別耗記

什麼是多執行緒/多程序 引用蟲師的解釋: 計算機程式只不過是磁碟中可執行的,二進位制(或其它型別)的資料。它們只有在被讀取到記憶體中,被作業系統呼叫的時候才開始它們的生命期。 程序(有時被稱為重量級程序)是程式的一次執行。每個程序都有自己的地址空間,記憶體,資料棧以及其它記錄其執行軌跡的輔助資料

2SolrJava後臺獲取Solr查詢資訊

這裡介紹兩種方法:1.Solrj,2.httpClient httpGet 1.Solrj 首先需要下載Solrj相關的jar包,其實在Solr的下載包中就已經包含了Solrj jar包和依賴的jar包,具體位置: 依賴:Solr解壓檔案\dist\sol

Java反射研究2

接Java反射研究(1) 九、呼叫特定方法 Method m = c1.getMethod("funcname",Class<?>...c);   //funcname表示呼叫方法的名稱,c表示引數的Class物件 例如:Method m = c1.getM

Java】DateUtil2

繼承 ava sim pla bool private throw ons tar import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat;

暑假自學JAVA Web心得2

聲明 代碼 請求 區別 處理請求 nbsp 編譯器 心得 最終 3.JSP腳本 1.JSP中應用代碼片段 格式:<% Java代碼或是腳本代碼 %> 在頁面請求處理 期間被執行。通過java代碼可以定義變量或是流程控制語句,通過腳本代碼可以應用JSP的內置對象

java入門學習2—基本數據類型

堆內存 類指針 erl 相互 lean 就是 沒有初始化 true ++ 1、變量:定義變量:【數據類型】 變量名 = 賦值(這樣定義的變量一般屬於局部變量,放置在棧內存中); 2、標識符:可以有字母(可以使任意文字),數字,下劃線,$等組成;但是不能以數字開頭,不能是保留

java基礎筆記2----流程控制

特性 byte 增加 基本 執行 size 判斷 efault 跳轉 java流程控制結構包括順序結構,分支結構,循環結構。 順序結構: 程序從上到下依次執行,中間沒有任何判斷和跳轉。 代碼如下: package c

java虛擬機4--類加載機制

sta 代理技術 賦值 工作 開始 外部 equals() ioe con 類加載機制 類是在運行期間第一次使用時動態加載的,而不是編譯時期一次性加載。因為如果在編譯時期一次性加載,那麽會占用很多的內存。 1.1 類的生命周期 包括以下 7 個階段: 加

Java筆試題2

spl res new void scan system.in lin () static /** * 一個物體從高h處下落,下落後會反彈到離地面高上一次下降高度的1/2,求當第m次接觸地面時走過路徑的長度 * 輸入: * 100,1