[英]How to hot swap java.lang Class in Java
我知道這不是一個很好的需求。 但是我現在需要這樣做。
我需要替換Class.getResourceAsStream(String)
的方法
但是ClassLoader.preDefineClass(String , ProtectionDomain)
檢查全名的類名是否以java.
開頭java.
,如果是,則拋出異常,並且不允許加載。
有沒有一種方法可以繞過運行時安全性檢查,而不是編譯時 ?
真正的要求是,許多較舊的項目需要遷移到新環境,因為其中一些項目可能已過時,而其中一些可能無法找到源代碼。 我需要在新環境中模擬舊環境,以便這些古老的項目可以繼續運行。
我嘗試了你給的方式。
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
上現有類的名稱定義類的嘗試都只能產生以下兩種結果之一:
如果現有的類已由相同的ClassLoader
定義,則再次定義它的嘗試將被拒絕,並將引發異常。 defineClass
不會更改現有的類,這根本不在其功能之內。
如果現有類已由其他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.