В вашем коде отсутствует шаг фактического копирования объектов класса из источника в место назначения:
try(FileInputStream in = new FileInputStream(new File("input.class")) {
ClassReader classReader = new ClassReader(in);
ClassWriter classWriter = new ClassWriter(classReader, 0);
classReader.accept(classWriter, 0);
Files.write(Paths.get("output.class"), classWriter.toByteArray());
}
Передача ClassReader
в конструктор ClassWriter
не копирует объекты, этоскорее включает оптимизацию, если ваше преобразование сохраняет большинство исходных файлов классов.Или, как указывает документация ClassWriter(ClassReader classReader, int flags)
:
Создает новый объект ClassWriter и позволяет оптимизировать «главным образом добавление» преобразований байт-кода.Эти оптимизации следующие:
- Методы постоянного пула и начальной загрузки из исходного класса копируются, как и в новый класс, что экономит время.Новые записи постоянного пула и новые методы начальной загрузки будут добавлены в конце при необходимости, но неиспользуемые записи постоянного пула или методы начальной загрузки не будут удалены .
- Методы, которые не преобразованы, копируютсякак и в новом классе, непосредственно из исходного байт-кода класса (т. е. без отправки событий посещения для всех инструкций метода), что экономит лот времени.Нетрансформированные методы обнаруживаются тем фактом, что ClassReader получает объекты MethodVisitor, которые поступают из ClassWriter (а не из любого другого экземпляра ClassVisitor).
Таким образом, когда вы соединяете ClassWriter
напрямуюв ClassReader
в методе accept
все посетители метода будут происходить из модуля записи, следовательно, все они будут скопированы напрямую.
Когда вы собираетесь существенно изменить класс или создаете новый класс, вместо этого вы бы использовали конструктор ClassWriter(int flags)
.
Обратите внимание, что COMPUTE_FRAMES
уже подразумевает COMPUTE_MAXS
.В приведенном выше примере я не указал ни один, так как методы все равно копируются.Если вы действительно хотите изменить или добавить код и вам нужно COMPUTE_FRAMES
, стоит указать SKIP_FRAMES
для считывателя, поскольку нет смысла декодировать исходные кадры, когда они все равно пересчитываются с нуля.
Таким образом, типичная установка преобразования выглядит следующим образом:
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor visitor = super.visitMethod(
access, name, desc, signature, exceptions);
if(method matches criteria) {
visitor = new MyMethodVisitorAdapter(visitor);
}
return visitor;
}
}
try(FileInputStream in = new FileInputStream(new File("input.class"))) {
ClassReader classReader = new ClassReader(in);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());
}
При связывании посетителей через конструкторы каждый метод, который вы не переопределяете, делегирует прикованному посетителю, реплицируя исходную конструкцию, когда завершаетсяцель ClassWriter
, соответственноMethodVisitor
предоставлено ClassWriter
.Если метод не удовлетворяет вашему условию преобразования, поэтому вы возвращаете исходное значение MethodVisitor
, описанная выше оптимизация по-прежнему применяется.Посетитель метода следует тому же шаблону, что и посетитель класса, переопределяя метод, который вы хотите перехватить.
Кстати, вам следует избегать смешивания старых операций ввода-вывода и NIO.Упрощенный вариант вашего кода выглядит следующим образом:
ClassReader classReader = new ClassReader(Files.readAllBytes(Paths.get("input.class")));
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
classReader.accept(new MyClassVisitor(classWriter), ClassReader.SKIP_FRAMES);
Files.write(Paths.get("output.class"), classWriter.toByteArray());
Обратите внимание на симметрию между чтением и записью
Хотя при использовании getResource
и др. Вы можетевынужден иметь дело с InputStream
.Но для классов, доступных через системный загрузчик классов, вы также можете просто передать имя класса конструктору ClassReader(String)
.