Как я могу заставить URLClassLoader вести себя так? - PullRequest
1 голос
/ 25 января 2010

Я пытаюсь сделать URLClassLoader, который ведет себя следующим образом:

  • Если запрашивается класс, имя которого входит в данный набор включений, загрузите его как обычно
  • В противном случае верните фиктивный класс

Я не могу заставить это работать. В приведенной ниже попытке я ожидаю, что SpecialClassLoader успешно загрузит Test$Thing. При этом я ожидаю, что он попытается загрузить Test$SuperThing, и я ожидаю, что все будет в порядке с тем фактом, что вместо него загружен фиктивный класс Nothing.

Однако что-то идет не так, и NoClassDefFoundError бросается в поисках Test$SuperThing.

Кто-нибудь знает, как это исправить?

public class Test {

    private static class SuperThing {}

    private static class Thing extends SuperThing {}

    public static void main(String[] args) {
        Set<String> inclusions = new HashSet<String>();
        inclusions.add("Test$Thing"); // note Test$SuperThing is excluded
        URLClassLoader cl = (URLClassLoader) 
            Thread.currentThread().getContextClassLoader();
        SpecialClassLoader cll = 
            new SpecialClassLoader(cl.getURLs(), inclusions);
        try {
            cll.loadClass("Test$Thing"); // line 22 (see stacktrace below)
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static class Nothing {}

    private static final class SpecialClassLoader extends URLClassLoader {

        private final Set<String> inclusions;

            public SpecialClassLoader(URL[] urls, Set<String> inclusions) {
                super(urls);
                this.inclusions = inclusions;
            }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (inclusions.contains(name)) {
                return findClass(name); // line 40 (see stacktrace below)
            }
            return Nothing.class;
        }
    }
}

РЕДАКТИРОВАТЬ: вот полученная мной трассировка стека (номера строк 22 и 40 указаны в листинге выше):


    Exception in thread "main" java.lang.NoClassDefFoundError: Test$SuperThing
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:56)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        at Test$SpecialClassLoader.loadClass(Test.java:40)
        at Test.main(Test.java:22)

Ответы [ 2 ]

4 голосов
/ 25 января 2010

Вот что происходит.

  • Когда вы пытаетесь загрузить Test $ Thing, метод defineClass выясняет, что ему нужно загрузить суперкласс Test $ Thing; т.е. тест $ SuperThing.

  • Системный загрузчик классов вызывает ваш загрузчик классов, который возвращает класс Nothing.

  • Загрузчик системного класса говорит: «Ух, у этого класса нет правильного полного имени класса !!» и выдает NoClassDefFoundError.

По сути, этот системный загрузчик классов защищает от вещей, которые могут дестабилизировать JVM. Если это позволяет пользовательскому загрузчику классов загружать неправильный класс, могут произойти неприятные вещи. Например, подумайте, что может произойти, если какой-то код вызовет какой-то метод, определенный для класса Test $ SuperThing, после того, как вы обманули загрузчик классов при загрузке класса Nothing.

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

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

0 голосов
/ 25 января 2010

Может ли класс с именем Test $ Thing.class быть найден в выходных данных компилятора? Поскольку код приведен здесь, на него никогда не будет прямой ссылки, и, возможно, компилятор оптимизировал класс. В этом случае отражение не может найти класс.

Если это так, вы можете просто добавить (не отражающую) ссылку на класс, чтобы он был скомпилирован.

РЕДАКТИРОВАТЬ: После того, как я попробовал это сам, я думаю, что нашел источник проблемы. Если я удалил 'extends SuperThing' из Thing-декларации, он пожалуется на то, что не нашел java.lang.Object. Вероятно, загрузчик классов пытается загрузить также все зависимые классы (включая те, которые он расширяет) и не может, потому что они не находятся в белом списке. Но это теория.

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