1. Обзор
В этой статье мы будем смотреть на Явасисст (Помощник по программированию Java) библиотека.
Проще говоря, эта библиотека упрощает процесс манипулирования Java-кодом с помощью API высокого уровня, чем api в JDK.
2. Зависимость от Maven
Чтобы добавить библиотеку Javassist в наш проект, мы должны добавить джавасистский в наш пом:
org.javassist javassist ${javaassist.version} 3.21.0-GA
3. Что такое литкод?
На очень высоком уровне каждый класс Java, написанный в простом текстовом формате и составленный в bytecode — набор инструкций, который может быть обработан виртуальной машиной Java. JVM переводит инструкции по кодым в инструкции по сборке машинного уровня.
Допустим, у нас есть Пункт класс:
public class Point { private int x; private int y; public void move(int x, int y) { this.x = x; this.y = y; } // standard constructors/getters/setters }
После компиляции Точка.class файл, содержащий литкод, будет создан. Мы можем увидеть интегри кода этого класса, выполняя Явап команда:
javap -c Point.class
Это будет печатать следующий выход:
public class com.baeldung.javasisst.Point { public com.baeldung.javasisst.Point(int, int); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: iload_1 6: putfield #2 // Field x:I 9: aload_0 10: iload_2 11: putfield #3 // Field y:I 14: return public void move(int, int); Code: 0: aload_0 1: iload_1 2: putfield #2 // Field x:I 5: aload_0 6: iload_2 7: putfield #3 // Field y:I 10: return }
Все эти инструкции указаны языком Java ; большое их количество доступно .
Давайте проанализируем инструкции по интеграм двигаться () метод:
- aload_0 инструкция загружает ссылку на стек из локальной переменной 0
- iload_1 загружает значение int из локальной переменной 1
- путфилд устанавливает поле x нашего объекта. Все операции аналогичны для полевых игрек
- Последней инструкцией является возвращать
Каждая строка Java-кода компилируется в bytecode с надлежащими инструкциями. Библиотека Javassist делает манипулирование этим ютекодом относительно простым.
4. Создание класса Java
Библиотека Javassist может быть использована для генерации новых файлов класса Java.
Допустим, мы хотим создать JavassistGeneratedClass класс, который реализует java.lang.Cloneable интерфейс. Мы хотим, чтобы у этого класса была id области int тип . КлассФиле используется для создания нового файла класса и ПолеИнфо используется для добавления нового поля в класс :
ClassFile cf = new ClassFile( false, "com.baeldung.JavassistGeneratedClass", null); cf.setInterfaces(new String[] {"java.lang.Cloneable"}); FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I"); f.setAccessFlags(AccessFlag.PUBLIC); cf.addField(f);
После того, как мы создадим JavassistGeneratedClass.class мы можем утверждать, что он на самом деле имеет id поле:
ClassPool classPool = ClassPool.getDefault(); Field[] fields = classPool.makeClass(cf).toClass().getFields(); assertEquals(fields[0].getName(), "id");
5. Загрузка инструкций по интеграм класса
Если мы хотим загрузить инструкции по коды уже существующего метода класса, мы можем получить КодАтрибуйте определенного метода класса. Тогда мы сможем получить Кодитератор итерировать все инструкции по ютекоду этого метода.
Давайте загрузим все инструкции по интеграм двигаться () метод Пункт класс:
ClassPool cp = ClassPool.getDefault(); ClassFile cf = cp.get("com.baeldung.javasisst.Point") .getClassFile(); MethodInfo minfo = cf.getMethod("move"); CodeAttribute ca = minfo.getCodeAttribute(); CodeIterator ci = ca.iterator(); Listoperations = new LinkedList<>(); while (ci.hasNext()) { int index = ci.next(); int op = ci.byteAt(index); operations.add(Mnemonic.OPCODE[op]); } assertEquals(operations, Arrays.asList( "aload_0", "iload_1", "putfield", "aload_0", "iload_2", "putfield", "return"));
Мы можем видеть все инструкции по интеграм двигаться () метод путем агрегирования bytecodes в список операций, как показано в утверждении выше.
6. Добавление полей к существующему классу Bytecode
Допустим, мы хотим добавить поле int введать в интегрод существующего класса. Мы можем загрузить этот класс с помощью КлассПолл и добавить в него поле:
ClassFile cf = ClassPool.getDefault() .get("com.baeldung.javasisst.Point").getClassFile(); FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I"); f.setAccessFlags(AccessFlag.PUBLIC); cf.addField(f);
Мы можем использовать отражение, чтобы проверить, что id поле существует на Пункт класс:
ClassPool classPool = ClassPool.getDefault(); Field[] fields = classPool.makeClass(cf).toClass().getFields(); ListfieldsList = Stream.of(fields) .map(Field::getName) .collect(Collectors.toList()); assertTrue(fieldsList.contains("id"));
7. Добавление конструктора в класс Bytecode
Мы можем добавить конструктора в существующий класс, упомянутый в одном из предыдущих примеров, используя добавитьИнвокспециальный () метод.
И мы можем добавить без параметров конструктор, ссылаясь на
ClassFile cf = ClassPool.getDefault() .get("com.baeldung.javasisst.Point").getClassFile(); Bytecode code = new Bytecode(cf.getConstPool()); code.addAload(0); code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V"); code.addReturn(null); MethodInfo minfo = new MethodInfo( cf.getConstPool(), MethodInfo.nameInit, "()V"); minfo.setCodeAttribute(code.toCodeAttribute()); cf.addMethod(minfo);
Мы можем проверить наличие вновь созданного конструктора, итерируя по tecode:
CodeIterator ci = code.toCodeAttribute().iterator(); Listoperations = new LinkedList<>(); while (ci.hasNext()) { int index = ci.next(); int op = ci.byteAt(index); operations.add(Mnemonic.OPCODE[op]); } assertEquals(operations, Arrays.asList("aload_0", "invokespecial", "return"));
8. Заключение
В этой статье мы представили библиотеку Javassist с целью упростить манипуляции с bytecode.
Мы сосредоточились на основных функциях и создали классный файл из Java-кода; мы также сделали некоторые манипуляции с интегром уже созданного класса Java.
Реализация всех этих примеров и фрагментов кода можно найти в Проект GitHub – это проект Maven, поэтому его нужно легко импортировать и запускать в том же объеме.