实现选择性类加载器
我想在加载时检测类路径上某些类的字节码。由于这些是第三方库,我确切地知道它们何时加载。问题是我需要有选择地进行检测,即仅检测某些类。现在,如果我不使用我的类加载器而是使用其父类来加载类,则该父类将被设置为类类加载器,并且所有简洁的类都由该父类加载,从而有效地使我的类加载器无法使用。所以我需要实现一个父级最后一个类加载器(请参阅如何将自定义类加载器放入使用?)。
所以我需要自己加载类。如果这些类是系统类(以“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
代码>本身不是...
I want to instrument the bytecode of some classes on the classpath at loading time. Since these are 3rd party libraries, I know exactly when they are loaded. The problem is that I need to do the instrumentation selectively, i.e. instrument only some classes. Now if I do not load a class with my classloader but with its parent, this parent gets set as the classes classloader and all succinct classes are loaded by that parent, effectively putting my classloader out of use. So I need to implement a parent-last classloader (see How to put custom ClassLoader to use?).
So I need to load classes myself. If those classes are system classes (starting with "java" or "sun") I delegate to the parent. Otherwise I read the bytecode and call defineClass(name, byteBuffer, 0, byteBuffer.length);
. But now a java.lang.ClassNotFoundException: java.lang.Object
is thrown.
Here is the code, any comment highly appreciated:
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);
}
}
}
The code can be executed e.g. with:
InstrumentingClassLoader instrumentingClassLoader = new InstrumentingClassLoader();
Class<?> changedClass = instrumentingClassLoader.loadClass(ClassLoaderTestSubject.class.getName());
The ClassLoaderTestSubject
should call some other classes, where the called classes are target of instrumentation, but the ClassLoaderTestSubject
itself is not...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我建议您使用常规的类加载器策略,即父级优先。但将您想要检测的所有类放入单独的 jar 文件中,并且不要将其添加到应用程序的类路径中。使用扩展 URL 类加载器并知道在其他位置搜索 jar 的类加载器实例化这些类。在这种情况下,所有 JDK 类都会自动识别,并且您的代码会更简单。您不必“思考”是否要检测该类:如果父类加载器未加载该类,则必须检测您的类。
I'd recommend you to use regular class loader strategy, i.e. parent first. But put all classes that you want to instrument into separate jar file and do not add it to the classpath of the application. Instantiate these classes using your class loader that extends URL class loader and knows to search jars in other location. In this case all JDK classes will be known automatically and your code will be simpler. You do not have to "think" whether to instrument the class: if it is not loaded by parent class loader it is your class that has to be instrumented.
愚蠢的错误。父类加载器不是继承层次结构中的父类加载器。它是赋予构造函数的父级。所以正确的代码如下所示:
Stupid mistake. The parent classloader is not the parent as in the inheritance hierarchy. It is the parent as given to the constructor. So the correct code looks like this: