Загрузить сгенерированный класс ASM во время выполнения

Я написал некоторый байт-код через ASM (сгенерированный класс реализует некоторый известный интерфейс).

У меня также есть некоторые зависимости от других классов.

Теперь я хочу подключить массив байтов к некоторому классу.

Как я могу загрузить класс из массива байтов?

Прямо сейчас я получаю NoClassDefFoundError: IllegalName: core/selecter/ObjectSelector/codegen/testClass.class

Я использую этот код для загрузки класса:

@SuppressWarnings("unchecked")
private static <T> SelectorAccess<T> createNewOrNull(byte[] bytesOfClass, String name) {
    try {
        return (SelectorAccess<T>) new ClassLoader() {
            public Class<?> defineClass(byte[] bytes) {
                return super.defineClass(name.concat(".class"), bytes, 0, bytes.length);
            }
        }.defineClass(bytesOfClass).newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
        return null;
    }
}

Я думаю, это потому, что мой ClassLoader не знает зависимостей моего класса?

Как я могу избежать этой проблемы?


person user3858583    schedule 05.07.2015    source источник
comment
1. Пожалуйста, всегда устанавливайте загрузчик родительского класса при определении нового загрузчика классов - также когда вы используете URLClassloader, иначе это может привести к проблемам позже (он может либо не найти эти зависимости, либо загрузить их новыми - но класс A загружается из CL1 != класс A загружается из CL2, хотя класс равен на уровне байтов!) 2. Вы должны вызвать loadClass в CL для загрузки класса и defineClass для загрузки байтов в память (будет вызываться loadClass/findClass, если он не может найти класс в своем кеше или ни один из его родителей еще не знает определения.   -  person Roman Vottner    schedule 05.07.2015
comment
какой родительский загрузчик классов я должен использовать? Thread.currentThread().getContextClassLoader() ?   -  person user3858583    schedule 05.07.2015
comment
это зависит от того, какой CL загрузил какие классы. Если у вас есть вложенная архитектура загрузчика классов (например, CL, который содержит общие классы, и вложенный CL, который содержит классы, уникальные для подключаемого модуля, например), вам необходимо передать CL, содержащий общие классы, в качестве родительского для CL, содержащего классы подключаемых модулей. Если у вас нет вложенной архитектуры, может быть достаточно getClass().getClassLoader(), если у вас есть CL для каждого потока, используйте вместо этого Thread.currentThread().getContextClassLoader()   -  person Roman Vottner    schedule 05.07.2015
comment
Ну, я использую getClass().getClassLoader(), и это работает. Однако это будет библиотека, не лучше ли позволить пользователю решать, какой загрузчик классов он хочет использовать, как будет выглядеть наилучшее значение по умолчанию? Thread.currentThread().getContextClassLoader() или getClass().getClassLoader()   -  person user3858583    schedule 06.07.2015
comment
почему бы не указать загрузчик классов для использования в качестве параметра? Так вы будете максимально гибкими. Но если ваша библиотека всегда загружается загрузчиком классов приложения во время запуска приложения, getClass().getClassLoader() может быть достаточно.   -  person Roman Vottner    schedule 06.07.2015
comment
Хорошо спасибо. Я позволю пользователю решить, какой загрузчик классов он хочет использовать. Однако значение по умолчанию будет class.getClassLoader(), которое вызовет исключение, если данный класс все равно не может быть найден.   -  person user3858583    schedule 06.07.2015


Ответы (1)


Ну, исключение указывает вам на это: «IllegalName: core/selecter/ObjectSelector/codegen/testClass.class». Это не допустимое имя класса, как ожидается ClassLoader.defineClass:

Параметры:

имя — ожидаемое бинарное имя класса или null, если неизвестно

Двоичные имена

Любое имя класса, указанное в виде параметра String. to методы в ClassLoader должно быть двоичным именем, как определено Спецификацией языка Java™.

Примеры допустимых имен классов включают:

"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"

Вы понимаете, что этот метод ожидает имя, разделенное точками, без суффикса .class. т.е. если внутреннее имя вашего класса в файле класса действительно core/selecter/ObjectSelector/codegen/testClass, правильное имя для defineClass будет core.selecter.ObjectSelector.codegen.testClass.

Однако обратите внимание на упоминание о возможности просто передать null в параметр name

person Holger    schedule 06.07.2015
comment
Спасибо за ответ. Однако у @Roman Vottner уже было решение. У моего сгенерированного вручную байт-кода были некоторые зависимости, которые мой пользовательский загрузчик классов не загружал. После того, как я использовал getClassLoader(), все заработало, как и ожидалось. - person user3858583; 07.07.2015
comment
@ user3858583: ну, зависимости - это еще одна проблема, но, учитывая код вашего вопроса и опубликованное исключение, вы так и не дошли до этого, поскольку эта попытка определить класс уже была отклонена из-за недопустимого имени до того, как какие-либо зависимости вступили в силу. Если ваше новое решение работает, это означает, что вы решили эту проблему как побочный эффект. Например, если вы предоставляете байт-код по запросу распознавателя JVM, вы, скорее всего, будете использовать имя, запрошенное JVM. И это имя, конечно, правильное. - person Holger; 07.07.2015