2011-07-19 89 views
1

我想在加載時檢測類路徑上某些類的字節碼。由於這些是第三方庫,我知道它們何時加載。問題是我需要有選擇地進行儀器儀表,即儀器只有一些類。現在,如果我沒有用我的類加載器加載一個類,而是用它的父類加載一個類,那麼這個父類會被設置爲類的類加載器,並且所有簡潔的類都由該父類加載,從而使我的類加載器無用。所以我需要實現一個父 - 最後一個類加載器(請參閱How to put custom ClassLoader to use?)。實現選擇性類加載器

所以我需要自己加載類。如果這些類是系統類(以「java」或「sun」開頭),則委託給父級。否則,我讀取字節碼並致電defineClass(name, byteBuffer, 0, byteBuffer.length);。但現在引發了java.lang.ClassNotFoundException: java.lang.Object

下面是代碼,高度讚賞任何評論:

public class InstrumentingClassLoader extends ClassLoader { 
private final BytecodeInstrumentation instrumentation = new BytecodeInstrumentation(); 

@Override 
public Class<?> loadClass(String name) throws ClassNotFoundException { 
    Class<?> result = defineClass(name); 
    if (result != null) { 
     return result; 
    } 
    result = findLoadedClass(name); 
    if(result != null){ 
     return result; 
    } 
    result = super.findClass(name); 
    return result; 
} 

private Class<?> defineClass(String name) throws ClassFormatError { 
    byte[] byteBuffer = null; 
    if (instrumentation.willInstrument(name)) { 
     byteBuffer = instrumentByteCode(name); 
    } 
    else { 
     byteBuffer = getRegularByteCode(name); 
    } 
    if (byteBuffer == null) { 
     return null; 
    } 
    Class<?> result = defineClass(name, byteBuffer, 0, byteBuffer.length); 
    return result; 
} 

private byte[] getRegularByteCode(String name) { 
    if (name.startsWith("java") || name.startsWith("sun")) { 
     return null; 
    } 
    try { 
     InputStream is = ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + ".class"); 
     ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 
     int nRead; 
     byte[] data = new byte[16384]; 
     while ((nRead = is.read(data, 0, data.length)) != -1) { 
      buffer.write(data, 0, nRead); 
     } 
     buffer.flush(); 
     return buffer.toByteArray(); 
    } catch (IOException exc) { 
     return null; 
    } 
} 

private byte[] instrumentByteCode(String fullyQualifiedTargetClass) { 
    try { 
     String className = fullyQualifiedTargetClass.replace('.', '/'); 
     return instrumentation.transformBytes(className, new ClassReader(fullyQualifiedTargetClass)); 
    } catch (Exception e) { 
     throw new RuntimeException(e); 
    } 
} 
} 

的代碼可以例如執行搭配:

InstrumentingClassLoader instrumentingClassLoader = new InstrumentingClassLoader(); 
    Class<?> changedClass = instrumentingClassLoader.loadClass(ClassLoaderTestSubject.class.getName()); 

ClassLoaderTestSubject應該調用一些其他類,其中稱爲類儀表的目標,但ClassLoaderTestSubject本身是不是...

回答

1

愚蠢的錯誤。父類加載器不是繼承層次結構中的父類。它是給構造函數的父類。因此,正確的代碼如下所示:

public InstrumentingClassLoader() { 
    super(InstrumentingClassLoader.class.getClassLoader()); 
    this.classLoader = InstrumentingClassLoader.class.getClassLoader(); 
} 

@Override 
public Class<?> loadClass(String name) throws ClassNotFoundException { 
    [... as above ...] 
    result = classLoader.loadClass(name); 
    return result; 
} 
1

我建議你使用常規的類加載器策略,即父母第一。但是,要將您要測試的所有類放入單獨的jar文件中,並且不要將其添加到應用程序的類路徑中。使用擴展URL類加載器的類加載器實例化這些類,並知道在其他位置搜索jar。在這種情況下,所有的JDK類將自動識別,並且您的代碼將更簡單。您不必「思考」是否需要使用該類:如果未由父類加載器加載,那麼您的類就必須進行檢測。

+0

當然,你是完全正確的。沒有任何其他限制,這將是最簡單和最優雅的方式。然而,我必須解決手頭的問題...... – roesslerj