Как использовать экземпляр анонимного класса в другом классе генерации байт-кода

Мне трудно использовать сгенерированный класс байт-кода, который загружается Unsafe.defineAnonymousClass(). Мне интересно, как использовать объект анонимного класса для инициализации другого класса (или анонимного класса).

Возьмем пример класса Callee ниже, например, его конструктор принимает Callee2 в качестве параметра.

Class Callee{
    Callee2 _call2;
    public Callee(Callee2 callee2){
        ...
    }
}

Во время выполнения я создал новые классы для Callee2 и Callee, и оба новых класса загружаются Unsafe.defineAnonymousClass(). Для нового Callee конструктор также изменяется на:

 public test.code.jit.asm.simple.Callee(test.code.jit.asm.simple.Callee2.1506553666);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: aload_0       
         1: invokespecial #65                 // Method java/lang/Object."<init>":()V
           ....
         8: return       

в то время как сгенерированное имя класса Callee2:

      class test.code.jit.asm.simple.Callee2/1506553666

Я создал экземпляр Callee2/1506553666 и хочу создать с ним экземпляр нового Callee, но не могу:

        newCls.getConstructor(args).newInstance(objs); 

где args = [class test.code.jit.asm.simple.Callee2/1506553666] и objs= [test.code.jit.asm.simple.Callee2/1506553666@39b0a038]

args[0] не имеет смысла, так как этот класс загружается анонимным загрузчиком (на анонимные классы не ссылаются никакие загрузчики классов). Так что меня действительно озадачивает, как передать объекты в массиве objs вызову метода.

Исключение возникает при вызове getConstructor (args) с сообщением:

java.lang.NoClassDefFoundError: test/code/jit/asm/simple/Callee2/1506553666
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2483)
    at java.lang.Class.getConstructor0(Class.java:2793)
    at java.lang.Class.getConstructor(Class.java:1708)
    at code.jit.asm.util.ReflectionUtil.adapt2GeneratedObject(ReflectionUtil.java:36)
    at code.jit.asm.services.BytecodeGenerator.generator(BytecodeGenerator.java:164)

Исключение явно для меня, поскольку анонимный класс является временным для любого загрузчика классов. Но в моем случае мне нужно инициализировать новый Callee (также анонимный класс) новым экземпляром Callee2 (байт-коды в конструкторе Callee будут считывать члены поля Callee2), так что есть ли обходной путь для передачи нового экземпляра callee2 для нового вызываемого конструктора?


person shijie xu    schedule 10.07.2015    source источник
comment
Я далек от того, чтобы быть экспертом в этом вопросе, но ясно, что байт-код класса Callee2 был неправильно введен в загрузчик классов. Вот почему он не может загрузить класс Calle2. Однажды (давным-давно) я сгенерировал динамический код, скомпилировал его с помощью инструментов компилятора и внедрил, но выбрал другой подход. Подробнее см. здесь.   -  person fps    schedule 11.07.2015


Ответы (1)


Взгляните на подпись и комментарий к документации, которого нет в стандартной документации API, поскольку он не является частью официального API:

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

  • Integer, Long, Float, Double: соответствующий тип объекта-оболочки из java.lang.
  • Utf8: строка (должен иметь подходящий синтаксис, если используется в качестве подписи или имени) Класс: любой объект java.lang.Class
  • Строка: любой объект (не только java.lang.String)
  • InterfaceMethodRef: (NYI) дескриптор метода для вызова аргументов этого сайта вызова.

… (параметры:)

cpPatches там, где существуют ненулевые записи, они заменяют соответствующие записи CP в данных

public native Class<?> defineAnonymousClass(
                       Class<?> hostClass, byte[] data, Object[] cpPatches);

Другими словами, вы можете предоставить массив того же размера, что и постоянный пул класса, который вы собираетесь определить. Держите null там, где вы не хотите его изменять. Прямо в тех местах, где ваш постоянный пул имеет CONSTANT_Class_info, представляющий анонимный класс, вы просто передаете связанный с ним объект Class в массиве. Таким образом, нет никакого поиска класса, вам даже не нужно указывать правильное имя класса в байтах класса.

Есть несколько очевидных ограничений:

  • Проблема возникнет, если у вас циклические зависимости, так как вам нужен уже существующий объект Class для исправления пула другого класса. Что ж, для использования классов, которые, как известно, разрешаются лениво, это может сработать.
  • Вы можете только заменить CONSTANT_Class_info на Class, которого достаточно, например, для доступ к членам этого класса или создание новых его экземпляров. Но это не помогает, когда анонимный класс является частью сигнатуры, то есть вы хотите объявить поле этого типа или использовать метод, имеющий его в качестве параметра или типа возвращаемого значения. Но вы можете получить доступ к таким методам, используя опцию исправления записи CONSTANT_InterfaceMethodref_info через MethodHandle (гм, когда-то реализовано, поскольку, я думаю, «NYI» означает «еще не реализовано»)…
person Holger    schedule 16.07.2015
comment
Я забыл упомянуть, что CONSTANT_Class_info можно использовать в приведении типов. Таким образом, чтобы решить проблему с подписью, конструктор должен принять Object (или любой другой неанонимный суперкласс) и привести его к анонимному классу. - person Holger; 16.07.2015
comment
Спасибо @Holger. Я пишу новый базовый класс, который расширяется всеми сгенерированными классами. Этот новый базовый класс используется в качестве параметра для всех конструкторов. Это похоже на пример компиляции в пакете ASM. - person shijie xu; 21.07.2015
comment
Я вернулся, чтобы увидеть ваше предложение построить cpPatch здесь, потому что мое существующее решение не может охватить все случаи. Предположим, что byte[] data, загружаемый Unsafe, представляет класс DAnouny, как здесь структурировать cpPatch для defineAnonymousClass(,,cpPatch) (CONSTANT_Class_info не объявлен)? Похоже, что cpPatch должна быть картой из концепции (gist.github.com/forax/0c25deac1867d1ef3247). - person shijie xu; 02.09.2015