2016-11-23 54 views
4

我有一個類如何在運行時創建一個Java類的副本?

class Foo { 
    int increment(int x) { 
     return x + 1; 
    } 
} 

我要獲得這個類的副本在運行時,E。 G。類似

class Foo$Copy1 { 
    int increment(int x) { 
     return x + 1; 
    } 
} 

它具有所有相同的方法,但名稱不同。

Proxy似乎幫助委託,但不複製他們的所有機構的方法。

+2

您爲什麼要這麼做?方法不會被實例化。你確定你在談論一個班級而不是一個對象嗎? –

+3

顯而易見的問題是:你爲什麼要這樣做?您不能將通過標準類加載器加載的類重新轉換爲其來自的類文件,您可以編寫自己的類加載器來攔截對「defineClass()」的調用。 – biziclop

+0

@biziclop我想保持複製方法中每個方法調用的簡介清晰單形。 – leventov

回答

8

您可以使用Byte Buddy此:

Class<?> type = new ByteBuddy() 
    .redefine(Foo.class) 
    .name("Foo$Copy1") 
    .make() 
    .load(Foo.class.getClassLoader()) 
    .getLoaded(); 

Method method = type.getDeclaredMethod("increment", int.class); 
int result = (Integer) method.invoke(type.newInstance(), 1); 

注意,這種方法重新定義內Foo,例如類的任何用途如果返回Foo的方法,它現在會返回Foo$Copy1。所有代碼參考都一樣。

+0

謝謝。它是否使用私有的ClassLoader的'defineClass()'方法或者換個角度? – leventov

+0

您可以選擇一個'ClassLoadingStrategy'作爲第二個參數。默認情況下,除非提供了引導類加載器,否則使用注入'Foo'的類加載器。使用'ClassLoadingStrategy.Default.WRAPPER',你也可以創建一個不需要的類加載器。 –

0

我認爲使用不安全可能是足夠的,如果你有正常的字節碼訪問。

類似Foo.class.getClassLoader().getResourceAsStream()可以給你類的字節碼。

然後使用 sun.misc.Unsafe.defineClass(String name, byte[] code, int off, int len, ClassLoader classLoader, ProtectionDomain protectionDomain)在與Foo相同的類加載器和保護域中定義類,但使用不同的名稱。

細節是要弄清楚的,但它可能是沒有任何第三方庫的最簡單的方法。

+0

可能ByteBuddy方法更靈活,更容易維護。 –

+2

儘管如此,您仍然需要重新定義類文件中的類名稱以及任何自引用。只使用'Foo'命名的類文件是行不通的。 –

2
+0

對於那些已經使用Javassist的人來說,這可能是最合適的解決方案。此外,我也沒有偏好Javassist和Byte Buddy – leventov

+0

@leventov另外,您也可以使用JDK的BCEL(它來自JDK,但不是公共API,因爲它位於「com.sun.org.apache.bcel.internal 「包)而不依賴於Javassist。 – sozal

+0

like this' String copyClassName = classToBeCopied.getName()+「$ Copy」; ClassGen classGen = new ClassGen(Repository.lookupClass(classToBeCopied)); classGen.setClassName(copyClassName); byte [] bytecode = classGen.getJavaClass()。getBytes(); 返回UNSAFE。的defineClass( copyClassName, 字節碼, 0, bytecode.length, classToBeCopied.getClassLoader(), classToBeCopied.getProtectionDomain()); ' – sozal