簡體   English   中英

是否可以使用 ClassLoader 偽造缺失的類?

[英]Is it possible to fake missing classes using ClassLoader?

我正在從實現公共 API 接口的 JAR 加載類。 接口本身將保持不變,但與 API 關聯的其他類可能會隨着時間而改變。 顯然,一旦 API 發生更改,我們將不再能夠支持使用舊版本編寫的接口的實現。 然而,一些接口方法提供了簡單的String類型的元數據,我們可以假設這些元數據永遠不會改變,也永遠不會依賴 API 中可能改變的其他部分。 即使 API 發生變化,我也希望能夠提取此元數據。

例如,考慮以下可能加載的實現,其中Foo是接口, Bar是 API 中的另一個 class。 即使 class Bar不再存在,我也想調用name方法。

class MyFoo implements Foo {
   Bar bar = null;

   @Override public String name() {
      return "MyFoo"
   }
}

據我所知,顯而易見的方法是在我的自定義ClassLoader中覆蓋loadClass(String name)並為Bar返回一些“假” class 。 可以假設元數據方法永遠不會創建或使用Bar object。 問題是當被要求加載Bar時如何生成這個“假” class 。 我考慮過以下方法:

  1. 只需返回任何舊的現有 class。 我已經嘗試返回Object.class ,但是當我嘗試實例化Foo的實例時,這仍然會導致BarNoClassDefFoundError
  2. 使用 ASM 從頭開始生成新 class 的字節碼。
  3. 使用 ASM 重命名某種空模板 class 以匹配Bar並加載它。

2. 和 3. 似乎都很復雜,所以我想知道是否有更簡單的方法來實現我的目標?

這是一個 class 加載程序,它將以非常簡單的方式為在搜索路徑上找不到的每個 class 創建一個虛擬 class:

public class DummyGeneratorLoader extends URLClassLoader {

    public DummyGeneratorLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public DummyGeneratorLoader(URL[] urls) {
        super(urls);
    }

    public DummyGeneratorLoader(
        URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(urls, parent, factory);
    }

    static final byte[] template = ("Êþº¾\0\0\0002\0\n\1\7\0\1\1\0\20java/lang/Object"
        + "\7\0\3\1\0\6<init>\1\0\3()V\14\0\5\0\6\n\0\4\0\7\1\0\4Code\0\1\0\2\0\4\0"
        + "\0\0\0\0\1\0\1\0\5\0\6\0\1\0\t\0\0\0\21\0\1\0\1\0\0\0\5*·\0\b±\0\0\0\0\0\0")
        .getBytes(StandardCharsets.ISO_8859_1);

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            return super.findClass(name);
        }
        catch(ClassNotFoundException ex) { }
        return new ByteArrayOutputStream(template.length + name.length() + 10) { {
            write(template, 0, 11);
            try { new DataOutputStream(this).writeUTF(name.replace('.', '/')); }
            catch (IOException ex) { throw new AssertionError(); }
            write(template, 11, template.length - 11);
        }
        Class<?> toClass(String name) {
            return defineClass(name, buf, 0, count); } }.toClass(name);
    }
}

但是,虛擬 class 無法滿足的使用代碼可能會帶來很多期望或結構限制。 畢竟,在調用接口方法之前,你必須創建一個 class 的實例,所以它必須通過驗證並成功執行其構造函數。

如果方法確實具有假定的結構,例如public String name() { return "MyFoo"; } public String name() { return "MyFoo"; }使用 ASM 可能是更簡單的選擇,但不是生成任意復雜的假環境,而是解析這些方法並預測它們返回的常量值。 這種方法將僅包含兩個指令, ldc valueareturn 您只需要檢查是否是這種情況並從第一條指令中提取值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM