Java 反射最終篇 - Mock 對象和樁

Mock 對象和 **樁(Stub)**在邏輯上都是 Optional 的變體。他們都是最終程序中所使用的“實際”對象的代理。
不過,Mock 對象和樁都是假扮成那些可以傳遞實際信息的實際對象,而不是像 Optional 那樣把包含潛在 null 值的對象隱藏。

Mock 對象和樁之間的的差別在於程度不同。

  • Mock 對象往往是輕量級的,且用於自測試。通常,爲了處理各種不同的測試場景,我們會創建出很多 Mock 對象。
  • 樁只是返回樁數據,通常是重量級的,在多個測試中被複用。可以根據它們被調用的方式,通過配置進行修改。因此,樁是一種複雜對象,可以做很多事情。
    至於 Mock 對象,如果你要做很多事,通常會創建大量又小又簡單的 Mock 對象。

接口和類型

interface 關鍵字的一個重要目標就是允許程序員隔離組件,進而降低耦合度。使用接口可以實現這一目標,但是通過類型信息,這種耦合性還是會傳播出去——接口並不是對解耦的一種無懈可擊的保障。
比如我們先寫一個接口:


實現這個接口

通過使用 RTTI,我們發現 a 是用 B 實現的。通過將其轉型爲 B,我們可以調用不在 A 中的方法。

這樣的操作完全是合情合理的,但是你也許並不想讓客戶端開發者這麼做,因爲這給了他們一個機會,使得他們的代碼與你的代碼的耦合度超過了你的預期。
你可能認爲 interface 關鍵字正在保護你,但其實並沒有。另外,在本例中使用 B 來實現 A 這種情況是有公開案例可查的。

一種解決方案是直接聲明,如果開發者決定使用實際的類而不是接口,他們需要自己對自己負責。這在很多情況下都是可行的,但“可能”還不夠,你或許希望能有一些更嚴格的控制方式。

最簡單的方式是讓實現類只具有包訪問權限,這樣在包外部的客戶端就看不到它了:

在包中唯一 public 的部分就是 HiddenC,在被調用時將產生 A接口類型的對象
即使你從 makeA() 返回的是 C 類型,你在包的外部仍舊不能使用 A 之外的任何方法,因爲你不能在包的外部命名 C

現在如果你試着將其向下轉型爲 C,則將被禁止,因爲在包的外部沒有任何 C 類型可用:

通過使用反射,仍然可以調用所有方法,甚至是 private 方法!如果知道方法名,你就可以在其 0Method 對象上調用 setAccessible(true),就像在 callHiddenMethod() 中看到的那樣。

你可能覺得,可以通過只發布編譯後的代碼來阻止這種情況,但其實這並不能解決問題。因爲只需要運行 javap(一個隨 JDK 發佈的反編譯器)即可突破這一限制。
使用 javap

javap -private C

-private 標誌表示所有的成員都應該顯示,甚至包括私有成員。下面是輸出:

class C extends
java.lang.Object implements typeinfo.interfacea.A {
  typeinfo.packageaccess.C();
  public void f();
  public void g();
  void u();
  protected void v();
  private void w();
}

因此,任何人都可以獲取你最私有的方法的名字和簽名,然後調用它們。

那如果把接口實現爲一個私有內部類,又會怎麼樣呢?下面展示了這種情況:

public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()

這裏對反射仍然沒有任何東西可以隱藏。那麼如果是匿名類呢?

輸出結果:

public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()

看起來任何方式都沒法阻止反射調用那些非公共訪問權限的方法。對於字段來說也是這樣,即便是 private 字段:
輸出結果:

i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!

但實際上 final 字段在被修改時是安全的。運行時系統會在不拋出異常的情況下接受任何修改的嘗試,但是實際上不會發生任何修改。

通常,所有這些違反訪問權限的操作並不是什麼十惡不赦的。如果有人使用這樣的技術去調用標誌爲 private 或包訪問權限的方法(很明顯這些訪問權限表示這些人不應該調用它們),那麼對他們來說,如果你修改了這些方法的某些地方,他們不應該抱怨。
另一方面,總是在類中留下後門,也許會幫助你解決某些特定類型的問題(這些問題往往除此之外,別無它法)。
總之,不可否認,反射給我們帶來了很多好處。

程序員往往對編程語言提供的訪問控制過於自信,甚至認爲 Java 在安全性上比其它提供了(明顯)更寬鬆的訪問控制的語言要優越。然而,正如你所看到的,事實並不是這樣。

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