我想在加载时检测类路径上某些类的字节码。由于这些是第三方库,我确切知道它们何时被加载。问题是我需要有选择地进行检测,即仅检测一些类。现在,如果我没有使用我的类加载器加载一个类但是使用它的父类,则将此父类设置为类类加载器,并且所有简洁类都由该父类加载,从而有效地使我的类加载器无法使用。所以我需要实现父级最后一个类加载器(参见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
本身不是......
答案 0 :(得分:1)
我建议您使用常规的类加载器策略,即父级优先。但是,将要修改的所有类放入单独的jar文件中,不要将其添加到应用程序的类路径中。使用扩展URL类加载器的类加载器实例化这些类,并且知道在其他位置搜索jar。在这种情况下,所有JDK类都将自动知道,您的代码将更简单。您不必“思考”是否要对该类进行检测:如果它没有被父类加载器加载,则必须对您的类进行检测。
答案 1 :(得分: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;
}