Я использую библиотеку asm для выполнения некоторых модификаций байт-кода Java - в частности, для модификации моих классов для реализации нового интерфейса и связанных методов. Мой текущий подход заключается в использовании основного asm API через javaagent. Я хотел бы сохранить этот динамический подход в отличие от статического изменения файлов .class.
На более высоком уровне моя проблема в том, что если я решу изменить класс A, который расширяется от B, мне также нужно будет изменить B. (Учитывая мое понимание того, как классы загружаются в JVM, я считаю, что класс B всегда будет передан трансформатору перед классом A. (Пожалуйста, поправьте меня, если я ошибаюсь). Учитывая это предположение, я думаю, что мне нужно вернуться и повторно преобразовать B. Мой подход таков: зафиксировано в этом фрагменте кода:
public byte[] transform(ClassLoader l, String name, Class<?> clazz, ProtectionDomain d, byte[] b) {
throws IllegalClassFormatException {
// **1**
System.out.println("--->>> " + name);
if (interestingClass(name)) {
try {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
PyClassVisitorAdapter pv = new PyClassVisitorAdapter(cw, name);
cr.accept(pv, 0);
// **2** Retrieve the superclass and try to transform that
if (! "Ljava/lang/Object;".equals(pv.getSuperName())) {
String cName = classJvmToCanonical(pv.getSuperName());
Class[] classes = inst.getAllLoadedClasses();
for (Class c : classes) {
if (c.getName().equals(cName)) {
inst.retransformClasses(c);
break;
}
}
}
// Dump the transformed class
ClassReader cr2 = new ClassReader(cw.toByteArray());
ClassWriter cw2 = new ClassWriter(cr2, 0);
TraceClassVisitor tcv = new TraceClassVisitor(cw2, new PrintWriter(System.out));
cr2.accept(tcv, 0);
return cw2.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
} else {
return b;
}
}
( inst
- дескриптор Instrumentation
, который передается в конструкторе)
Часть, с которой мне трудно справиться - это блок, отмеченный в комментариях с помощью ** 2 **
. Давайте еще раз скажем, что A расширяет B, и я «заинтересован» в преобразовании A.Я ожидаю, что имя суперкласса (B) будет напечатано по адресу ** 1 **
(но не будет преобразовано, потому что я не думаю, что это интересно на первом проходе) а затем, когда я доберусь до ** 2 **
и обнаружу, что суперкласс A - это B, я должен попытаться повторно преобразовать B. На этом этапе я ожидаю, что этот метод будет вызван снова (через inst.retransformClasses ()
), и я бы увидел, что B будет напечатан по адресу ** 1 **
. Однако я этого не делаю. (Я добавил операторы печати и уверен, что достигаю вызова повторного преобразования. Я также проверил, что Instrumentation.isRetransformClassesSupported ()
и Instrumentation.isModifiableClass (c)
оба возвращают значение true ).
Я считаю, что правильно настроил агент; установка для Can-Retransform-Classes и Can-Redefine-Classes значения true в манифесте. Кроме того, когда я добавляю преобразователь в Инструментарий в методе агента premain
, я делаю следующее:
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new PyClassFileTransformer(inst), true);
}
Есть какие-нибудь идеи относительно того, что я здесь делаю не так? Спасибо.