«1. Обзор

В этой статье мы рассмотрим библиотеку Javasisst (помощник по программированию на Java).

Проще говоря, эта библиотека упрощает процесс управления байт-кодом Java за счет использования высокоуровневого API, чем в JDK.

2. Зависимость от Maven

Чтобы добавить библиотеку Javassist в наш проект, нам нужно добавить javassist в наш pom:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>${javaassist.version}</version>
</dependency>

<properties>
    <javaassist.version>3.21.0-GA</javaassist.version>
</properties>

3. Что такое байт-код?

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

Допустим, у нас есть класс Point:

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
}

После компиляции будет создан файл Point.class, содержащий байт-код. Мы можем увидеть байт-код этого класса, выполнив команду javap:

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."<init>":()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; их имеется большое количество.

Давайте проанализируем инструкции байт-кода метода move():

    Инструкция aload_0 загружает ссылку в стек из локальной переменной 0 iload_1 загружает значение int из локальной переменной 1 putfield устанавливает поле x из наш объект. Все операции аналогичны для поля y. Последняя инструкция является возвратом

Каждая строка Java-кода компилируется в байт-код с соответствующими инструкциями. Библиотека Javassist позволяет относительно легко манипулировать этим байт-кодом.

4. Создание класса Java

Библиотека Javassist может использоваться для создания новых файлов классов Java.

Допустим, мы хотим сгенерировать класс JavassistGeneratedClass, реализующий интерфейс java.lang.Cloneable. Мы хотим, чтобы этот класс имел поле id типа int. ClassFile используется для создания нового файла класса, а FieldInfo используется для добавления нового поля в класс:

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. Загрузка инструкций байт-кода класса

Если мы хотим загрузить инструкции байт-кода уже существующего метода класса, мы можем получить CodeAttribute определенного метода класса. Затем мы можем заставить CodeIterator перебирать все инструкции байт-кода этого метода.

Давайте загрузим все инструкции байт-кода метода move() класса Point:

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();

List<String> operations = 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"));

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

6. Добавление полей в байт-код существующего класса

Допустим, мы хотим добавить поле типа int в байт-код существующего класса. Мы можем загрузить этот класс с помощью ClassPoll и добавить в него поле:

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 существует в классе Point:

ClassPool classPool = ClassPool.getDefault();
Field[] fields = classPool.makeClass(cf).toClass().getFields();
List<String> fieldsList = Stream.of(fields)
  .map(Field::getName)
  .collect(Collectors.toList());
 
assertTrue(fieldsList.contains("id"));

7. Добавление конструктора в байт-код класса ~~ ~ Мы можем добавить конструктор к существующему классу, упомянутому в одном из предыдущих примеров, с помощью метода addInvokespecial().

И мы можем добавить конструктор без параметров, вызвав метод \u003cinit\u003e из класса java.lang.Object:

Мы можем проверить наличие вновь созданного конструктора, перебирая байт-код:

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);

8. Заключение

CodeIterator ci = code.toCodeAttribute().iterator();
List<String> operations = 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"));

В этой статье мы представили библиотеку Javassist с целью упростить работу с байт-кодом.

Мы сосредоточились на основных функциях и создали файл класса из кода Java; мы также сделали некоторые манипуляции с байт-кодом уже созданного класса Java.

Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.

«