Я хочу обработать байт-код некоторых классов на пути к классам во время загрузки. Поскольку это сторонние библиотеки, я точно знаю, когда они загружаются. Проблема в том, что мне нужно делать выборочно, то есть только некоторые классы. Теперь, если я не загружаю класс с моим загрузчиком классов, но с его родителем, этот родительский объект устанавливается как загрузчик классов, и все сжатые классы загружаются этим родителем, что фактически выводит мой загрузчик классов из использования. Поэтому мне нужно реализовать последний загрузчик классов для родителя (см. Как установить собственный ClassLoader для использования? ).
Так что мне нужно загрузить классы самостоятельно. Если эти классы являются системными классами (начиная с «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
не является ...