簡體   English   中英

如何在Java中熱交換java.lang類

[英]How to hot swap java.lang Class in Java

我知道這不是一個很好的需求。 但是我現在需要這樣做。

我需要替換Class.getResourceAsStream(String)的方法

但是ClassLoader.preDefineClass(String , ProtectionDomain)檢查全名的類名是否以java.開頭java. ,如果是,則拋出異常,並且不允許加載。

有沒有一種方法可以繞過運行時安全性檢查,而不是編譯時

更新資料

真正的要求是,許多較舊的項目需要遷移到新環境,因為其中一些項目可能已過時,而其中一些可能無法找到源代碼。 我需要在新環境中模擬舊環境,以便這些古老的項目可以繼續運行。

Upadte(GhostCat)

我嘗試了你給的方式。

ClassLoader類

public class ClassLoaderTest extends ClassLoader {
    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("getResourceAsStream " + name);
        return new ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Load class " + name);
        String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
        InputStream is = getClass().getResourceAsStream(fileName);

        if (is == null) {
            return super.loadClass(name);
        }

        byte[] b = new byte[0];
        try {
            b = new byte[is.available()];
            is.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return defineClass(name, b, 0, b.length);
    }
}

調用類

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
    ClassLoaderTest classLoaderTest = new ClassLoaderTest();
    Class clazz = Class.forName("com.Client", true, classLoaderTest);
    Object client = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("method");
    method.invoke(client);
}

客戶類別

public class Client {
    public void method() {
        System.out.println(this.getClass().getResourceAsStream("aaa"));
        System.out.println(Class.class.getResourceAsStream("bbb"));
        System.out.println("".getClass().getResourceAsStream("ccc"));
    }
}

它確實與this.getClass().getResourceAsStream(String) ,但與Class.class.getResourceAsStream(String) / "".getClass().getResourceAsStream(String) 因為我無法使用我的自定義ClassLoader加載類,該類名稱以java.開頭java.

你不能

該類由引導類加載器加載。 而且您無法更改此人在做什么-它是用本機代碼編寫的。 換句話說:這是JVM的核心-不允許您篡改。

這就是Java安全性概念的“整體思路”:用戶無法“進入” JVM平台所保證的事物。

鑒於OP所做的編輯是關於遺留代碼的:似乎您希望針對屬於JVM的類-屬於您自己的代碼的類加入Class.getResourceAsStream(String)

如果是這樣的話,那么“答案”很簡單:你必須確保所有類都是由你的類加載器加載。 getResourceAsStreeam()的Javadoc明確指出:

查找具有給定名稱的資源。 搜索與給定類關聯的資源的規則由該類的定義類加載器實現。 該方法委托給該對象的類加載器。

因此:了解如何使用自定義類加載器。 例如從這里開始。

為嘗試以java.開頭的合格名稱定義類而引發異常java. ,是defineClass合同的defineClass ,但這無關緊要。 任何使用ClassLoader上現有類的名稱定義類的嘗試都只能產生以下兩種結果之一:

  1. 如果現有的類已由相同的ClassLoader定義,則再次定義它的嘗試將被拒絕,並將引發異常。 defineClass不會更改現有的類,這根本不在其功能之內。

  2. 如果現有類已由其他ClassLoader (或引導加載程序)加載,則可以在此ClassLoader上定義具有相同限定名稱的ClassLoader ,但這將是一個完全不同的類,與具有相同名稱的類無關,但一個不同的定義類加載器。

您不能為java.*類使用相同的名稱和不同的加載器來定義此類,這只是一個附加限制,但是您的嘗試無論如何還是行不通的。

如果要在類ClassLoader后替換它,則只能使用代理,例如使用Instrumentation API的Java代理或調試器。 另外,您可以使用-Xbootclasspath/p:…命令行選項將您自己的版本簡單地添加到引導類路徑。

當然,用特定版本替換它只能在派生該版本的環境中起作用。 同樣,不允許將此類應用程序公開部署。

一個更通用的解決方案是使用當前版本的ClassLoader並即時對其進行修改的Instrumentation Agent,但是,如果您必須選擇開發字節碼轉換Agent,則轉換該Code將會更加容易和可靠。 應用程序代碼 ,只需用自定義方法的調用替換Class.getResourceAsStream(String)所有調用,而無需觸摸不需要這種修改的代碼。

我寫了一篇關於如何在此處實現運行時類轉換的冗長答案: 如何在運行時重新轉換類

一種更簡單的方法是位於https://github.com/nickman/retransformer的 retransformer項目,該項目將轉換java。*類,包括本機方法。 (我用它來修改Runtime.availableProcessors)

暫無
暫無

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

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