Реализация выборочного ClassLoader - PullRequest
1 голос
/ 19 июля 2011

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

Ответы [ 2 ]

1 голос
/ 20 июля 2011

глупая ошибка.Родительский загрузчик классов не является родительским, как в иерархии наследования.Это родитель, как дано конструктору.Итак, правильный код выглядит так:

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 голос
/ 19 июля 2011

Я бы порекомендовал вам использовать обычную стратегию загрузки классов, то есть родительский в первую очередь. Но поместите все классы, которые вы хотите применить в отдельный файл jar, и не добавляйте его в путь к классам приложения. Создайте эти классы с помощью загрузчика классов, который расширяет загрузчик классов URL и знает, как искать файлы в другом месте. В этом случае все классы JDK будут известны автоматически, и ваш код будет проще. Вам не нужно «думать», следует ли применять инструмент к классу: если он не загружен родительским загрузчиком классов, то это ваш класс, который должен быть инструментирован.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...