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

Я пытаюсь изменить следующие поля в конструкторе класса с помощью javassist:

Label Label1 = new Label(new StringBuilder().append(user.name));
Label Label2 = new Label(new StringBuilder().append(user.time.toString());

Я хочу добавить текст к двум меткам. К тексту можно получить доступ и установить его с помощью методов getText() и setText().

Как я мог этого добиться?


person user922764    schedule 05.12.2012    source источник


Ответы (1)


Самый простой подход — использовать возможность модифицировать тело конструктора с помощью java-кода и позволить javassist создать байт-код.

Таким образом, вы можете легко сделать что-то вроде:

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("package1.package2.ClassToInject");
    /* Notice that in this case I'm going for the default constructor
     * If you want another constructor you just have to materialize the CtClass, for
     * each parameter and pass them in the CtClass Array
     */
CtConstructor declaredConstructor = ctClass.getDeclaredConstructor(new CtClass[] {}); 
 /* Now that you have your constructor you can use insertAfter(), this means, it 
      * will be the last thing to be executed in the constructor. We'll rewrite the 
      * label1 field with our new value. Notice that the string in insertAfter 
      * argument is a regular, valid java code line.
      */
    declaredConstructor.insertAfter("Label1 = new package3.package4.Label(new StringBuilder().append(\"somePrefixMayBeAStringOrAVariableInScope\").append(user.name));");

    // and finally we write the bytecode
ctClass.writeFile("/somePathToPutTheInjectedClassFile/");

Также имейте в виду, что если префикс, который вы будете добавлять, вместо строки является статическим полем в другом классе, вы должны указать полное имя этого класса, например: .append(package1.package2.SomeClass.SomeField).

Это необходимо, потому что imports находятся только на уровне исходного кода, когда вы смотрите на байт-код JVM, все ссылки на классы относятся к полному квалифицированному имени.

Дополнительную информацию о том, как добиться такого рода изменений с помощью Javassist, можно найти в документации javasssist, раздел 4.1 Вставка исходного текста в начало/конец тела метода

ОБНОВЛЕНИЕ

Всякий раз, когда вы пишете код Java для внедрения javassist, имейте в виду, что вы должны использовать полные имена классов, иначе пул классов javassist не сможет найти классы, что приведет к ошибке javassist.CannotCompileException.

person pabrantes    schedule 05.12.2012
comment
Вместо getDeclaredConstructor я использовал getConstructors()[0], так как есть только один конструктор. Попытка изменить индекс и получение ArrayOutOfBoundException, похоже, указывает на то, что эта часть работает. Однако insertAfter выдает следующую ошибку: Исключение в основном потоке javassist.CannotCompileException: [исходная ошибка] нет такого класса: метка - person user922764; 09.12.2012
comment
@ user922764: это потому, что пул классов javassist не может найти класс Label, вы должны определить полное имя класса, например new package1.package2.Label(....). Я отредактировал ответ, чтобы отразить эту деталь. - person pabrantes; 09.12.2012
comment
К сожалению, это тоже не сработало. В документации сказано, что доступ к локальным переменным, объявленным в методе, запрещен. Метки объявляются внутри конструктора, но сами по себе не сохраняются как атрибуты класса. Вместо этого они добавляются в макет, который сам является атрибутом класса. Так что, похоже, мне придется пройти через этот макет, чтобы получить доступ к ярлыкам. Извините, я не уточнил это раньше, я просто это заметил. Всё усложняется :( - person user922764; 10.12.2012
comment
Можете ли вы отредактировать свой вопрос, чтобы добавить полный код конструктора, чтобы вам было легче ориентироваться. Несмотря на то, что у вас были Label1 label = new Label, я ошибочно подумал, что это атрибуты класса. - person pabrantes; 10.12.2012