Подтвердить что ты не робот

Пользовательский загрузчик классов Java не используется для загрузки зависимостей?

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

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Loading: " + name);
        return super.loadClass(name);
    }
}     

Он просто выплескивает имя всех классов, которые он загружает. Однако, когда я пытаюсь запустить некоторый код,

import org.python.util.PythonInterpreter;
public class Scripts {
    public String main(){

        PythonInterpreter p = new PythonInterpreter();
        p.exec("print 'Python ' + open('.gitignore').read()");

        return "Success! Nothing broke";
    }
}

с помощью

MyClassLoader bcl = new MyClassLoader();
Class c = bcl.loadClass("Scripts");

Method m = c.getMethod("main");
String result = (String) m.invoke(c.getConstructor().newInstance());

выводит

Loading: Scripts
Loading: java.lang.Object
Loading: java.lang.String
Loading: org.python.util.PythonInterpreter
Python build/
.idea/*
*.iml
RESULT: Success! Nothing broke

Что кажется довольно странным. org.python.util.PythonInterpreter не является простым классом, и он зависит от целой группы других классов в пакете org.python.util. Эти классы явно загружаются, поскольку код exec 'd python способен делать вещи и читать мой файл. По какой-то причине эти классы не загружаются загрузчиком классов, загружаемым PythonInterpreter.

Почему? У меня создалось впечатление, что загрузчик классов, используемый для загрузки класса C, будет использоваться для загрузки всех других классов, необходимых для C, но это явно не происходит здесь. Это ошибочное предположение? Если это так, как мне настроить его таким образом, чтобы все транзитивные зависимости C загружались моим загрузчиком классов?

EDIT:

Некоторые эксперименты с использованием URLClassLoader, которые были предложены. Я изменил делегацию в loadClass():

try{
    byte[] output = IOUtils.toByteArray(this.getResourceAsStream(name));
    return instrument(defineClass(name, output, 0, output.length));
}catch(Exception e){
    return instrument(super.loadClass(name));
}

а также сделать подкласс MyClassLoader URLClassLoader, а не простой ClassLoader, захватить URL-адреса с помощью:

super(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

Но это не кажется правильным. В частности, getResourceAsStream() бросает на него значения null для всех классов, которые я запрашиваю, даже несистемные классы, подобные Jython lib.

4b9b3361

Ответ 1

Основы загрузки классов

Существует два основных места для расширения загрузчика классов, чтобы изменить способ загрузки классов:

  • findClass (String name) - вы переопределяете этот метод, когда хотите найдите класс с обычной родительской первой делегацией.
  • loadClass (имя строки, логическое решение) - переопределить этот метод, если вы хотите изменить способ выполнения делегирования загрузки класса.

Однако классы могут исходить только из окончательных методов defineClass (...), предоставляемых java.lang.ClassLoader. Поскольку вы хотите захватить все загружаемые классы, нам нужно будет переопределить loadClass (String, boolean) и использовать вызов defineClass (...) где-то в нем.

ПРИМЕЧАНИЕ. Внутри методов defineClass (...) существует привязка JNI к нативной стороне JVM. Внутри этого кода есть проверка классов в пакетах java. *. Он будет загружать только эти классы загрузчиком системного класса. Это мешает вам возиться с внутренними компонентами самой Java.

Пример Child First ClassLoader

Это очень простая реализация ClassLoader, которую вы пытаетесь создать. Предполагается, что все классы, которые вам нужны, доступны для загрузчика родительского класса, поэтому он просто использует родительский элемент в качестве источника для байтов класса. Эта реализация использует Apache Commons IO для краткости, но ее можно легко удалить.

import java.io.IOException;
import java.io.InputStream;

import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.commons.io.IOUtils.closeQuietly;
...
public class MyClassLoader
  extends ClassLoader {
  MyClassLoaderListener listener;

  MyClassLoader(ClassLoader parent, MyClassLoaderListener listener) {
    super(parent);
    this.listener = listener;
  }

  @Override
  protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    // respect the java.* packages.
    if( name.startsWith("java.")) {
      return super.loadClass(name, resolve);
    }
    else {
      // see if we have already loaded the class.
      Class<?> c = findLoadedClass(name);
      if( c != null ) return c;

      // the class is not loaded yet.  Since the parent class loader has all of the
      // definitions that we need, we can use it as our source for classes.
      InputStream in = null;
      try {
        // get the input stream, throwing ClassNotFound if there is no resource.
        in = getParent().getResourceAsStream(name.replaceAll("\\.", "/")+".class");
        if( in == null ) throw new ClassNotFoundException("Could not find "+name);

        // read all of the bytes and define the class.
        byte[] cBytes = toByteArray(in);
        c = defineClass(name, cBytes, 0, cBytes.length);
        if( resolve ) resolveClass(c);
        if( listener != null ) listener.classLoaded(c);
        return c;
      } catch (IOException e) {
        throw new ClassNotFoundException("Could not load "+name, e);
      }
      finally {
        closeQuietly(in);
      }
    }
  }
}

И это простой интерфейс прослушивателя для просмотра загрузки классов.

public interface MyClassLoaderListener {
  public void classLoaded( Class<?> c );
}

Затем вы можете создать новый экземпляр MyClassLoader с текущим загрузчиком классов в качестве родителя и контролировать классы по мере их загрузки.

MyClassLoader classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
  public void classLoaded(Class<?> c) {
    System.out.println(c.getName());
  }
});
classLoader.loadClass(...);

Это будет работать в самом общем случае и позволит вам получать уведомления о загрузке классов. Однако, если какой-либо из этих классов создает свои собственные загрузчики первого класса, они могут обойти код уведомления, добавленный здесь.

Более продвинутая загрузка класса

Чтобы действительно загружать загружаемые классы, даже когда загрузчик дочерних классов переопределяет loadClass (String, boolean), вам необходимо вставить код между загружаемыми вами классами и любые вызовы, которые они могут сделать для ClassLoader.defineClass(...). Для этого вам нужно начать переписывать байтовый код с помощью инструмента, такого как ASM. У меня есть проект под названием Chlorine в GitHub, который использует этот метод для перезаписи вызовов конструктора java.net.URL. Если вам интересно, как возиться с классами во время загрузки, я бы проверял, что проект вышел.

Ответ 2

Если вы делаете

    System.out.println( p.getClass().getClassLoader() );

вы увидите, что p classloader не является вашим MyClassLoader bcl. Фактически он загружался родителем bcl, загрузчиком системного класса.

Когда PythonInterpreter загружает свои зависимые классы, он будет использовать свой фактический загрузчик классов, системный загрузчик классов, а не ваш bcl, поэтому ваш перехват не будет достигнут.

Чтобы решить проблему, ваш загрузчик классов не может делегировать его родительскому элементу, он должен фактически загружать классы самостоятельно.

Для этого вы можете подклассом URLClassLoader (красть URL-адреса от системного загрузчика).

Ответ 3

Если вы хотите распечатать классы по мере их загрузки, как насчет включения опции verbose: class в JVM?

java -verbose:class your.class.name.here

Чтобы ответить на ваши прямые вопросы:

Почему? У меня создалось впечатление, что загрузчик классов, используемый для загрузки класса C, будет использоваться для загрузки всех других классов, необходимых для C, но это явно не происходит здесь. Это ошибочное предположение? Если это так, как мне настроить его таким образом, чтобы все транзитивные зависимости C загружались моим загрузчиком классов?

При поиске в ClassLoaders поиск выполняется из листового класса ClassLoader в корневой каталог, когда Java работает над новым классом, он должен быть загружен, он выполняется из корня дерева ClassLoader, вплоть до листа, который инициировал класса.

Почему? Подумайте, хотел ли ваш пользовательский класс загрузить что-то из стандартных библиотек Java. Правильный ответ заключается в том, что это должно быть загружено System ClassLoader, чтобы класс мог быть максимально общим. Особенно, если вы считаете, что загружаемый класс может потенциально загрузить намного больше классов.

Это также решает проблему, которая потенциально может привести к множеству экземпляров системных классов, загружаемых в разные классы ClassLoaders - каждый с тем же полным именем. EDIT Классы будут правильно решены в ClassLoader. Однако есть две проблемы.

  • Скажем, у нас есть два экземпляра String, a и b. a.getClass().isInstance(b) и a.getClass() == b.getClass() не являются истинными, если a и b были созданы в разных ClassLoaders. Это вызовет ужасные проблемы.
  • Синглоты: они не будут синглтонами - у вас может быть один на ClassLoader.

END EDIT

Еще одно замечание. Так же, как вы настроили ClassLoader специально для загрузки классов, интерпретаторы часто сами создают экземпляры ClassLoader, в которые они загружают среду интерпретации и script. Таким образом, если изменяется script, ClassLoader можно удалить (и с ним script) и перезагрузить в новом ClassLoader. EJB и сервлеты также используют этот трюк.

Ответ 4

что, если вы переопределите другой метод loadClass()?

protected Class<?> loadClass(String name, boolean resolve)

Ответ 5

Вы можете использовать объект PySystemState, чтобы указать пользовательский загрузчик классов, прежде чем создавать экземпляр PythonInterpreter.

PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);

http://wiki.python.org/jython/LearningJython