Пакет java.lang.classfile
Предоставляет библиотеку для анализа, создания и преобразования файлов классов.
Пакетjava.lang.classfile содержит модели API для чтения, записи и изменения файлов классов Java, как указано в главе 4 Спецификации виртуальной машины Java. Этот пакет, java.lang.classfile.attribute, java.lang.classfile.constantpool и java.lang.classfile.instruction образуют API файлов классов. Чтение файлов классов
Основным классом для чтения файлов классов являетсяClassModel; мы преобразуем байты в ClassModel с помощью ClassFile.parse(byte[]): ClassModel cm = ClassFile.of().parse(bytes);
parse, позволяющих указать различные параметры обработки. ClassModel — это неизменяемое описание файла класса. Он предоставляет методы доступа к метаданным класса (например, ClassModel.thisClass(), ClassModel.flags()), а также к подчинённым сущностям файла класса (ClassModel.fields(), AttributedElement.attributes()). Объект ClassModel загружается лениво; большинство частей файла класса анализируются только при необходимости. Из-за ленивой загрузки эти модели могут быть небезопасны для использования в нескольких потоках. Кроме того, вызовы методов доступа к моделям могут приводить к IllegalArgumentException из-за некорректного формата файла
class, поскольку анализ выполняется лениво.
Перечислить имена полей и методов класса можно следующим образом:
ClassModel cm = ClassFile.of().parse(bytes);
for (FieldModel fm : cm.fields())
System.out.printf("Field %s%n", fm.fieldName().stringValue());
for (MethodModel mm : cm.methods())
System.out.printf("Method %s%n", mm.methodName().stringValue());
При перечислении методов мы получаем MethodModel для каждого метода; подобно ClassModel, он предоставляет доступ к метаданным метода и возможность переходить к подчинённым сущностям, например к байт-коду тела метода. Таким образом, ClassModel является корнем дерева с дочерними элементами, соответствующими полям, методам и атрибутам, а у MethodModel, в свою очередь, есть собственные дочерние элементы (атрибуты, CodeModel и т. д.).
Такие методы, как ClassModel.methods(), позволяют явно обходить структуру класса, сразу переходя к интересующим нас частям. Это полезно для некоторых видов анализа, но для обработки всего файла класса может потребоваться более организованный подход. ClassModel также предоставляет представление файла класса в виде последовательности элементов класса, которыми могут быть методы, поля, атрибуты и другие сущности, различаемые с помощью сопоставления с образцом. Предыдущий пример можно переписать так:
ClassModel cm = ClassFile.of().parse(bytes);
for (ClassElement ce : cm) {
switch (ce) {
case MethodModel mm -> System.out.printf("Method %s%n", mm.methodName().stringValue());
case FieldModel fm -> System.out.printf("Field %s%n", fm.fieldName().stringValue());
default -> { }
}
}
Модели, возвращаемые в качестве элементов при обходе ClassModel, в свою очередь могут служить источниками элементов. Чтобы обойти файл класса и перечислить все классы, к которым осуществляется доступ через поля и методы, можно выбрать элементы класса, описывающие методы, затем выбрать элементы методов, описывающие атрибут кода, и, наконец, выбрать элементы кода, описывающие инструкции доступа к полям и вызова:
ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies = new HashSet<>();
for (ClassElement ce : cm) {
if (ce instanceof MethodModel mm) {
for (MethodElement me : mm) {
if (me instanceof CodeModel xm) {
for (CodeElement e : xm) {
switch (e) {
case InvokeInstruction i -> dependencies.add(i.owner().asSymbol());
case FieldInstruction i -> dependencies.add(i.owner().asSymbol());
default -> { }
}
}
}
}
}
}
Этот же запрос можно было бы обработать как конвейер потока по элементам класса:
ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies =
cm.elementStream()
.flatMap(ce -> ce instanceof MethodModel mm ? mm.elementStream() : Stream.empty())
.flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty())
.<ClassDesc>mapMulti((xe, c) -> {
switch (xe) {
case InvokeInstruction i -> c.accept(i.owner().asSymbol());
case FieldInstruction i -> c.accept(i.owner().asSymbol());
default -> { }
}
})
.collect(toSet());
Модели и элементы
Представление файлов классов в этом API основано на понятиях моделей и элементов. Модели представляют сложные структуры, такие как классы, методы, поля, элементы записей или тело кода метода. Модели можно исследовать либо с помощью произвольной навигации (например, через метод доступаClassModel.methods()), либо как линейную последовательность элементов. (Элементы также могут быть моделями; FieldModel также является элементом класса.) Для каждого типа модели (например, MethodModel) существует соответствующий тип элемента (MethodElement). Модели и элементы неизменяемы и загружаются лениво, поэтому создание модели не обязательно требует обработки всего её содержимого. Пул констант
Значительная часть интересного содержимого файла класса находится в пуле констант.ClassModel предоставляет лениво загружаемое представление пула констант только для чтения с помощью ClassModel.constantPool(). Описания содержимого файла класса часто предоставляются в виде различных подтипов PoolEntry, таких как ClassEntry или Utf8Entry. Записи пула констант также доступны через модели и элементы; в приведённом выше примере обхода элемент InvokeInstruction предоставляет метод для owner, соответствующий записи
Constant_Class_info в пуле констант.
Атрибуты
Значительная часть содержимого файла класса хранится в атрибутах; атрибуты встречаются у классов, методов, полей, компонентов записей и у атрибутаCode. Большинство атрибутов представлены как элементы; например, SignatureAttribute является ClassElement, MethodElement и FieldElement, поскольку он может присутствовать во всех этих местах и включается при переборе элементов соответствующей модели. Некоторые атрибуты не представлены как элементы; это атрибуты, тесно связанные с другими частями файла класса и логически являющиеся их частью. К ним относятся атрибуты BootstrapMethods, LineNumberTable,
StackMapTable, LocalVariableTable и
LocalVariableTypeTable. Они обрабатываются библиотекой и считаются частью структуры, с которой связаны (записи атрибута BootstrapMethods считаются частью пула констант; номера строк и метаданные локальных переменных моделируются как элементы CodeModel.)
Атрибут Code, помимо представления в виде MethodElement, также является самостоятельной моделью (CodeModel) из-за своей сложной структуры.
Для каждого стандартного атрибута существует интерфейс (в java.lang.classfile.attribute), который предоставляет содержимое атрибута и фабрики для его создания. Например, атрибут Signature определяется классом SignatureAttribute и предоставляет методы доступа к SignatureAttribute.signature(), а также фабрики, принимающие Utf8Entry или String.
Пользовательские атрибуты
Атрибуты преобразуются между форматом файла класса и соответствующей объектной формой с помощьюAttributeMapper. Объект
AttributeMapper предоставляет метод AttributeMapper.readAttribute(AttributedElement, ClassReader, int) для преобразования формата файла класса в экземпляр атрибута и метод AttributeMapper.writeAttribute(BufWriter, Attribute) для обратного преобразования в формат файла класса. Он также содержит метаданные, включая имя атрибута, набор сущностей файла класса, к которым применим атрибут, и сведения о том, разрешено ли несколько атрибутов одного типа для одной сущности. Для каждого типа атрибутов, определённых в разделе 4.7 Спецификации виртуальной машины Java, существуют встроенные сопоставители атрибутов (в Attributes), а также сопоставители нескольких распространённых нестандартных атрибутов, используемых JDK, таких как CharacterRangeTable.
Нераспознанные атрибуты предоставляются как элементы типа UnknownAttribute, обеспечивающие доступ только к содержимому атрибута byte[].
Для нестандартных атрибутов пользовательские сопоставители атрибутов можно указать с помощью параметра файла класса ClassFile.AttributeMapperOption.of(Function). Реализации пользовательских атрибутов должны наследовать CustomAttribute.
Параметры
ClassFile.of(ClassFile.Option[]) принимает список параметров. ClassFile.Option — это базовый интерфейс для некоторых статически перечисляемых параметров, а также для фабрик более сложных параметров, в том числе:
-
ClassFile.AttributeMapperOption.of(Function)-- указание формата пользовательских атрибутов -
ClassFile.AttributesProcessingOption-- обработка нераспознанных или проблемных исходных атрибутов (по умолчаниюPASS_ALL_ATTRIBUTES) -
ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver)-- указание пользовательского средства разрешения иерархии классов, используемого при создании карт стека -
ClassFile.ConstantPoolSharingOption-- совместное использование пула констант при преобразовании (по умолчаниюSHARED_POOL) -
ClassFile.DeadCodeOption-- удаление недостижимого кода (по умолчаниюPATCH_DEAD_CODE) -
ClassFile.DeadLabelsOption-- фильтрация неразрешённых меток (по умолчаниюFAIL_ON_DEAD_LABELS) -
ClassFile.DebugElementsOption-- обработка отладочной информации, например метаданных локальных переменных (по умолчаниюPASS_DEBUG) -
ClassFile.LineNumbersOption-- обработка номеров строк (по умолчаниюPASS_LINE_NUMBERS) -
ClassFile.ShortJumpsOption-- автоматическое преобразование коротких переходов в длинные при необходимости (по умолчаниюFIX_SHORT_JUMPS) -
ClassFile.StackMapsOption-- создание карт стека (по умолчаниюSTACK_MAPS_WHEN_REQUIRED)
ClassFile.AttributeMapperOption и ClassFile.ClassHierarchyResolverOption критически важны для корректного анализа и создания файлов class. Для анализа пользовательских атрибутов необходим сопоставитель атрибутов. Корректное средство разрешения необходимо для создания файлов class, байт-код которых содержит ссылки на классы, недоступные системному загрузчику классов, а также в некоторых особых случаях, когда при создании нужно избежать загрузки системных классов, например в агентах.
Большинство параметров позволяют запросить пропуск определённых частей файла класса при обходе, например отладочной информации или нераспознанных атрибутов. Некоторые параметры позволяют отключить создание частей файла класса, например карт стека. Многие из этих параметров позволяют находить компромисс между функциональностью и производительностью: обработка отладочной информации и номеров строк требует затрат (как при записи, так и при чтении). Если эти сведения не нужны, их можно исключить с помощью параметров, чтобы повысить производительность.
Запись файлов классов
Создание файлов классов выполняется с помощью построителей. Для каждого типа сущности, имеющего модель, существует и соответствующий тип построителя; классы создаются с помощьюClassBuilder, методы — с помощью MethodBuilder и т. д. Вместо непосредственного создания построителей они передаются в качестве аргумента пользовательской лямбда-функции. Чтобы создать знакомую программу «Hello, world!», мы запрашиваем построитель класса и используем его для создания построителей методов для конструктора и метода main, а затем используем построители методов для создания атрибута Code и построители кода для генерации инструкций:
byte[] bytes = ClassFile.of().build(CD_Hello,
clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
ClassFile.ACC_PUBLIC,
mb -> mb.withCode(
cob -> cob.aload(0)
.invokespecial(ConstantDescs.CD_Object,
ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
.return_()))
.withMethod("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
mb -> mb.withCode(
cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
.ldc("Hello World")
.invokevirtual(CD_PrintStream, "println", MTD_void_String)
.return_())));
Методы-обёртки ClassBuilder.buildMethodBody позволяют запросить у ClassBuilder создание построителей кода для непосредственного построения тел методов, минуя пользовательскую лямбда-функцию построителя метода:
byte[] bytes = ClassFile.of().build(CD_Hello,
clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
ClassFile.ACC_PUBLIC,
cob -> cob.aload(0)
.invokespecial(ConstantDescs.CD_Object,
ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
.return_())
.withMethodBody("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
.ldc("Hello World")
.invokevirtual(CD_PrintStream, "println", MTD_void_String)
.return_()));
Построители часто поддерживают несколько способов представления одной и той же сущности на разных уровнях абстракции. Например, инструкцию invokevirtual для вызова println можно было бы сгенерировать с помощью CodeBuilder.invokevirtual, CodeBuilder.invoke или CodeBuilder.with.
Метод-обёртка CodeBuilder.invokevirtual ведёт себя так, как будто вызывает метод-обёртку CodeBuilder.invoke, который, в свою очередь, ведёт себя так, как будто вызывает метод CodeBuilder.with. Такое объединение вызовов методов построителя позволяет объединять преобразования (описанные далее).
Если не указано иное, передача аргумента null конструктору или методу любого класса или интерфейса API файлов классов приводит к выбрасыванию NullPointerException. Кроме того, вызов метода с массивом или коллекцией, содержащими элемент null, приводит к выбрасыванию NullPointerException, если не указано иное.
Символьная информация
Для описания символьной информации о классах и типах API использует абстракции номинальных дескрипторов изjava.lang.constant, такие как ClassDesc и MethodTypeDesc; это снижает вероятность ошибок по сравнению с использованием обычных строк. Если у записи пула констант есть номинальное представление, она предоставляет метод, возвращающий соответствующий тип номинального дескриптора; например, метод ClassEntry.asSymbol() возвращает ClassDesc.
В подходящих случаях построители предоставляют два метода для создания элемента с символьной информацией: один принимает номинальные дескрипторы, а другой — записи пула констант.
Проверки согласованности, синтаксиса и верификация
При создании или преобразовании файлов классов проверки согласованности не выполняются (за исключением проверки аргументов на null). Все построители и фабричные методы элементов файла класса принимают предоставленную информацию без неявной проверки. Однако критические несоответствия (например, недопустимая последовательность кода или неразрешённые метки) влияют на внутренние инструменты и могут позднее вызвать исключения в процессе создания файла класса. Такие критические исключения выбрасываются какIllegalArgumentException. Использование номинальных дескрипторов гарантирует, что библиотека API файлов классов применит правильную сериализуемую форму с учётом фактического контекста. Кроме того, эти номинальные дескрипторы проверяются при создании, поэтому случайно создать их с недопустимым содержимым невозможно. В следующем примере имя класса передаётся методу ClassDesc.of(String) для проверки, а при сериализации записи класса в пуле констант библиотека автоматически преобразует имя класса в правильную внутреннюю форму.
var validClassEntry = constantPoolBuilder.classEntry(ClassDesc.of("mypackage.MyClass"));
С другой стороны, можно использовать методы построителей и фабрики, напрямую принимающие записи пула констант. Записи пула констант также можно создавать непосредственно из исходных значений, без дополнительного преобразования или проверки. В следующем примере намеренно используется неправильная форма имени класса, которая применяется без какой-либо проверки или преобразования.
var invalidClassEntry = constantPoolBuilder.classEntry(
constantPoolBuilder.utf8Entry("mypackage.MyClass"));
Более сложную верификацию файла класса можно выполнить, вызвав ClassFile.verify(ClassModel).
Преобразование файлов классов
API обработки файлов классов чаще всего используются для объединения чтения и записи при преобразовании: файл класса считывается, в него вносятся локальные изменения, а большая его часть передаётся без изменений. Для каждого типа построителя вXxxBuilder есть метод with(XxxElement), позволяющий напрямую передавать построителю элементы, которые нужно оставить без изменений. Чтобы удалить методы, имена которых начинаются с «debug», можно получить существующий ClassModel, создать новый файл класса с помощью ClassBuilder, перебрать элементы исходного ClassModel и передать их построителю, за исключением методов, которые нужно удалить:
ClassModel classModel = ClassFile.of().parse(bytes);
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (!(ce instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"))) {
classBuilder.with(ce);
}
}
});
Таким образом, построителю передаётся каждый элемент класса, кроме тех, которые соответствуют методам с именами, начинающимися с debug. Преобразования, конечно, могут быть более сложными: можно переходить к телам методов и инструкциям и преобразовывать их, но на каждом уровне повторяется одна и та же структура, поскольку для каждой сущности существуют соответствующие абстракции модели, построителя и элемента.
Преобразование можно рассматривать как операцию «flatMap» над последовательностью элементов: для каждого элемента его можно оставить без изменений, удалить или заменить одним либо несколькими элементами. Поскольку преобразование — распространённая операция над файлами классов, для каждого типа модели существует соответствующий тип
XxxTransform (описывающий преобразование последовательности
XxxElement), а каждый тип построителя имеет методы transformYyy для преобразования дочерних моделей. Преобразование — это функциональный интерфейс, принимающий построитель и элемент; его реализация применяет «flatMap» к элементам и передаёт их построителю. Предыдущий пример можно выразить так:
ClassTransform ct = (builder, element) -> {
if (!(element instanceof MethodModel mm && mm.methodName().stringValue().startsWith("debug")))
builder.with(element);
};
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);
Метод-обёртка ClassTransform.dropping позволяет упростить создание того же преобразования и выразить предыдущий пример следующим образом:
ClassTransform ct = ClassTransform.dropping(
element -> element instanceof MethodModel mm
&& mm.methodName().stringValue().startsWith("debug"));
Подъём преобразований
Хотя пример с использованием преобразований лишь немного короче, преимущество такого представления заключается в том, что операции преобразования проще объединять. Предположим, что нужно перенаправить вызовы статических методов вFoo на соответствующий метод в Bar. Это можно выразить как преобразование элементов CodeElement: CodeTransform fooToBar = (b, e) -> {
if (e instanceof InvokeInstruction i
&& i.owner().name().equalsString("Foo")
&& i.opcode() == Opcode.INVOKESTATIC) {
// remove the old element i by doing nothing to the builder
// add a new invokestatic instruction to the builder
b.invokestatic(CD_Bar, i.name().stringValue(), i.typeSymbol(), i.isInterface());
} else {
b.with(e); // leaves the element in place
}
};
Затем можно поднять это преобразование элементов кода до преобразования элементов методов. Оно перехватывает элементы методов, соответствующие атрибуту Code, переходит к его элементам кода, применяет к ним преобразование кода и передаёт остальные элементы методов без изменений:
MethodTransform mt = MethodTransform.transformingCode(fooToBar);
а затем дополнительно поднять преобразование элементов методов до преобразования элементов класса:
ClassTransform ct = ClassTransform.transformingMethods(mt);
или напрямую поднять преобразование кода до преобразования класса:
ClassTransform ct = ClassTransform.transformingMethodBodies(fooToBar);
и затем преобразовать файл класса:
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);
Это гораздо лаконичнее (и менее подвержено ошибкам), чем эквивалентный вариант с непосредственным обходом структуры файла класса:
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
classBuilder -> {
for (ClassElement ce : classModel) {
if (ce instanceof MethodModel mm) {
classBuilder.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(),
mm.flags().flagsMask(),
methodBuilder -> {
for (MethodElement me : mm) {
if (me instanceof CodeModel xm) {
methodBuilder.withCode(codeBuilder -> {
for (CodeElement e : xm) {
if (e instanceof InvokeInstruction i && i.owner().asInternalName().equals("Foo")
&& i.opcode() == Opcode.INVOKESTATIC)
codeBuilder.invoke(i.opcode(), CD_Bar,
i.name().stringValue(), i.typeSymbol(), i.isInterface());
else codeBuilder.with(e);
}});
}
else
methodBuilder.with(me);
}
});
}
else
classBuilder.with(ce);
}
});
Комбинирование преобразований
Преобразования элементов одного типа можно последовательно комбинировать, передавая результат первого на вход второго. Предположим, что нужно инструментировать все вызовы методов, выводя имя метода перед его вызовом:CodeTransform instrumentCalls = (b, e) -> {
if (e instanceof InvokeInstruction i) {
b.getstatic(CD_System, "out", CD_PrintStream)
.ldc(i.name().stringValue())
.invokevirtual(CD_PrintStream, "println", MTD_void_String);
}
b.with(e);
};
Затем можно объединить fooToBar и instrumentCalls с помощью CodeTransform.andThen(CodeTransform):
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes),
ClassTransform.transformingMethods(
MethodTransform.transformingCode(
fooToBar.andThen(instrumentCalls))));
instrumentCalls получит все элементы кода, созданные преобразованием forToBar, — как элементы кода исходного файла класса, так и замены (то есть вызовы методов Foo, заменённые вызовами Bar). Совместное использование пула констант
Преобразование не только упрощает чтение, преобразование элементов и запись. В большинстве случаев при преобразовании файла класса изменения относительно невелики. Для оптимизации таких случаев при преобразовании новый файл класса инициализируется копией пула констант исходного файла; это позволяет добиться существенных оптимизаций (непреобразованные методы и атрибуты можно обрабатывать, копируя их байты целиком, вместо анализа и повторной генерации их содержимого). Если совместное использование пула констант не требуется, его можно отключить с помощью параметраClassFile.ConstantPoolSharingOption. Это может быть полезно, если при преобразовании удаляется много элементов и в результате остаётся много неиспользуемых записей пула констант. Обработка неизвестных элементов файла класса при преобразовании
Пользовательские преобразования файлов классов могут не учитывать элементы, появившиеся в будущих выпусках JDK. Для обеспечения детерминированной стабильности преобразования файлов классов, которым необходимо обрабатывать все элементы, следует реализовывать так, чтобы они выбрасывали исключения при запуске на более новой версии JDK, если преобразуемый файл класса имеет более новую версию или если появляется новый неизвестный элемент файла класса. Например, это можно сделать с помощью следующих фрагментов строгой проверки совместимости:CodeTransform fooToBar = (b, e) -> {
if (ClassFile.latestMajorVersion() > ClassFile.JAVA_22_VERSION) {
throw new IllegalArgumentException("Cannot run on JDK > 22");
}
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
default -> b.with(e);
}
};
ClassTransform fooToBar = (b, e) -> {
switch (e) {
case ClassFileVersion v when v.majorVersion() > ClassFile.JAVA_22_VERSION ->
throw new IllegalArgumentException("Cannot transform class file version " + v.majorVersion());
default -> doSomething(b, e);
}
};
CodeTransform fooToBar = (b, e) -> {
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
case BranchInstruction i -> doSomething(b, i);
case ConstantInstruction i -> doSomething(b, i);
case ConvertInstruction i -> doSomething(b, i);
case DiscontinuedInstruction i -> doSomething(b, i);
case FieldInstruction i -> doSomething(b, i);
case InvokeDynamicInstruction i -> doSomething(b, i);
case InvokeInstruction i -> doSomething(b, i);
case LoadInstruction i -> doSomething(b, i);
case StoreInstruction i -> doSomething(b, i);
case IncrementInstruction i -> doSomething(b, i);
case LookupSwitchInstruction i -> doSomething(b, i);
case MonitorInstruction i -> doSomething(b, i);
case NewMultiArrayInstruction i -> doSomething(b, i);
case NewObjectInstruction i -> doSomething(b, i);
case NewPrimitiveArrayInstruction i -> doSomething(b, i);
case NewReferenceArrayInstruction i -> doSomething(b, i);
case NopInstruction i -> doSomething(b, i);
case OperatorInstruction i -> doSomething(b, i);
case ReturnInstruction i -> doSomething(b, i);
case StackInstruction i -> doSomething(b, i);
case TableSwitchInstruction i -> doSomething(b, i);
case ThrowInstruction i -> doSomething(b, i);
case TypeCheckInstruction i -> doSomething(b, i);
case PseudoInstruction i -> doSomething(b, i);
default ->
throw new IllegalArgumentException("An unknown instruction could not be handled by this transformation");
}
};
И наоборот, преобразованиям файлов классов, которым нужно обрабатывать лишь часть элементов, не требуется учитывать новые неизвестные элементы, и они могут передавать их дальше без изменений. В следующем примере показано такое преобразование кода, совместимое с будущими версиями:
CodeTransform fooToBar = (b, e) -> {
switch (e) {
case ArrayLoadInstruction i -> doSomething(b, i);
case ArrayStoreInstruction i -> doSomething(b, i);
default -> b.with(e);
}
};
Соглашения API
API в основном основан на модели данных формата файла класса, которая определяет виды элементов (включая модели и атрибуты) и их свойства. Для каждого вида элемента существует соответствующий интерфейс, описывающий этот элемент, а также фабричные методы для его создания. Для некоторых видов элементов соответствующий построитель также предоставляет методы-обёртки (например, CodeBuilder.invokevirtual(ClassDesc, String, MethodTypeDesc)).
Большая часть символьной информации в элементах представлена записями пула констант (например, владелец поля представлен объектом ClassEntry.) Фабрики и построители также принимают номинальные дескрипторы из java.lang.constant (например, ClassDesc.)
Модель данных
Каждый вид элемента определяется его именем, необязательным индикатором кратности (ноль или более, ноль или один, ровно один) и списком компонентов. Элементами класса являются поля, методы и атрибуты, которые могут присутствовать у классов:ClassElement =
FieldModel*(UtfEntry name, Utf8Entry descriptor)
| MethodModel*(UtfEntry name, Utf8Entry descriptor)
| ModuleAttribute?(int flags, ModuleEntry moduleName, UtfEntry moduleVersion,
List<ModuleRequireInfo> requires, List<ModuleOpenInfo> opens,
List<ModuleExportInfo> exports, List<ModuleProvidesInfo> provides,
List<ClassEntry> uses)
| ModulePackagesAttribute?(List<PackageEntry> packages)
| ModuleTargetAttribute?(Utf8Entry targetPlatform)
| ModuleHashesAttribute?(Utf8Entry algorithm, List<HashInfo> hashes)
| ModuleResolutionAttribute?(int resolutionFlags)
| SourceFileAttribute?(Utf8Entry sourceFile)
| SourceDebugExtensionsAttribute?(byte[] contents)
| CompilationIDAttribute?(Utf8Entry compilationId)
| SourceIDAttribute?(Utf8Entry sourceId)
| NestHostAttribute?(ClassEntry nestHost)
| NestMembersAttribute?(List<ClassEntry> nestMembers)
| RecordAttribute?(List<RecordComponent> components)
| EnclosingMethodAttribute?(ClassEntry className, NameAndTypeEntry method)
| InnerClassesAttribute?(List<InnerClassInfo> classes)
| PermittedSubclassesAttribute?(List<ClassEntry> permittedSubclasses)
| DeclarationElement*
DeclarationElement — элементы, общие для всех объявлений (классов, методов и полей), поэтому они выделены отдельно: DeclarationElement =
SignatureAttribute?(Utf8Entry signature)
| SyntheticAttribute?()
| DeprecatedAttribute?()
| RuntimeInvisibleAnnotationsAttribute?(List<Annotation> annotations)
| RuntimeVisibleAnnotationsAttribute?(List<Annotation> annotations)
| CustomAttribute*
| UnknownAttribute*
CodeModel (которая моделирует атрибут Code вместе с атрибутами, связанными с кодом: таблицей карт стека, таблицей локальных переменных, таблицей номеров строк и т. д.). FieldElement =
DeclarationElement
| ConstantValueAttribute?(ConstantValueEntry constant)
MethodElement =
DeclarationElement
| CodeModel?()
| AnnotationDefaultAttribute?(ElementValue defaultValue)
| MethodParametersAttribute?(List<MethodParameterInfo> parameters)
| ExceptionsAttribute?(List<ClassEntry> exceptions)
CodeModel уникальна тем, что её элементы упорядочены. Элементы Code включают обычный байт-код, а также ряд псевдоинструкций, представляющих цели ветвлений, метаданные номеров строк, метаданные локальных переменных и блоки перехвата исключений. CodeElement = Instruction | PseudoInstruction
Instruction =
LoadInstruction(TypeKind type, int slot)
| StoreInstruction(TypeKind type, int slot)
| IncrementInstruction(int slot, int constant)
| BranchInstruction(Opcode opcode, Label target)
| LookupSwitchInstruction(Label defaultTarget, List<SwitchCase> cases)
| TableSwitchInstruction(Label defaultTarget, int low, int high,
List<SwitchCase> cases)
| ReturnInstruction(TypeKind kind)
| ThrowInstruction()
| FieldInstruction(Opcode opcode, FieldRefEntry field)
| InvokeInstruction(Opcode opcode, MemberRefEntry method, boolean isInterface)
| InvokeDynamicInstruction(InvokeDynamicEntry invokedynamic)
| NewObjectInstruction(ClassEntry className)
| NewReferenceArrayInstruction(ClassEntry componentType)
| NewPrimitiveArrayInstruction(TypeKind typeKind)
| NewMultiArrayInstruction(ClassEntry componentType, int dims)
| ArrayLoadInstruction(Opcode opcode)
| ArrayStoreInstruction(Opcode opcode)
| TypeCheckInstruction(Opcode opcode, ClassEntry className)
| ConvertInstruction(TypeKind from, TypeKind to)
| OperatorInstruction(Opcode opcode)
| ConstantInstruction(ConstantDesc constant)
| StackInstruction(Opcode opcode)
| MonitorInstruction(Opcode opcode)
| NopInstruction()
PseudoInstruction =
| LabelTarget(Label label)
| LineNumber(int line)
| ExceptionCatch(Label tryStart, Label tryEnd, Label handler, ClassEntry exception)
| LocalVariable(int slot, UtfEntry name, Utf8Entry type, Label startScope, Label endScope)
| LocalVariableType(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope)
| CharacterRange(int rangeStart, int rangeEnd, int flags, Label startScope, Label endScope)
- Версия:
- 24
| Класс | Описание |
|---|---|
| AccessFlags | Моделирует флаги доступа класса, метода или поля. |
| Annotation | |
| AnnotationElement | |
| AnnotationValue | Моделирует структуру element_value или значение пары «элемент—значение» аннотации, как определено в JVMS 4.7.16.1. |
| AnnotationValue.OfAnnotation | Моделирует значение-аннотацию пары «элемент—значение». |
| AnnotationValue.OfArray | Моделирует значение-массив пары «элемент—значение». |
| AnnotationValue.OfBoolean | Моделирует логическое значение пары «элемент—значение». |
| AnnotationValue.OfByte | Моделирует значение типа byte пары «элемент—значение». |
| AnnotationValue.OfChar | Моделирует значение типа char пары «элемент—значение». |
| AnnotationValue.OfClass | Моделирует значение-класс пары «элемент—значение». |
| AnnotationValue.OfConstant | Моделирует значение-константу пары «элемент—значение». |
| AnnotationValue.OfDouble | Моделирует значение типа double пары «элемент—значение». |
| AnnotationValue.OfEnum | Моделирует значение-перечисление пары «элемент—значение». |
| AnnotationValue.OfFloat | Моделирует значение типа float пары «элемент—значение». |
| AnnotationValue.OfInt | Моделирует значение типа int пары «элемент—значение». |
| AnnotationValue.OfLong | Моделирует значение типа long пары «элемент—значение». |
| AnnotationValue.OfShort | Моделирует значение типа short пары «элемент—значение». |
| AnnotationValue.OfString | Моделирует строковое значение пары «элемент—значение». |
| Attribute<A extends Attribute<A>> | Моделирует атрибут (JVMS 4.7) в формате файла class. |
| AttributedElement | Элемент ClassFileElement, описывающий структуру файла class с атрибутами, например файл class, поле, метод, атрибут Code или компонент записи. |
| AttributeMapper<A extends Attribute<A>> | Двунаправленное сопоставление между представлением атрибута в файле class и его моделью API. |
| AttributeMapper.AttributeStability | Указывает зависимость данных от представления атрибута в файле class. |
| Attributes | Сопоставители атрибутов для предопределённых (JVMS 4.7) и специфичных для JDK нестандартных атрибутов. |
| BootstrapMethodEntry | Моделирует запись в таблице методов загрузки. |
| BufWriter | Расширенные возможности записи файлов class для AttributeMapper. |
| ClassBuilder | Построитель файла class. |
| ClassElement | Маркерный интерфейс для элемента-члена ClassModel. |
| ClassFile | Предоставляет возможность анализировать, преобразовывать и генерировать файлы class. |
| ClassFile.AttributeMapperOption | Параметр, описывающий определённые пользователем атрибуты для анализа файлов class. |
| ClassFile.AttributesProcessingOption | Параметр, определяющий, следует ли сохранять или отбрасывать атрибуты, корректность которых невозможно проверить после преобразования. |
| ClassFile.ClassHierarchyResolverOption | Параметр, определяющий средство разрешения иерархии классов, используемое при генерации карт стека или проверке классов. |
| ClassFile.ConstantPoolSharingOption | Параметр, определяющий, следует ли расширять исходный пул констант при преобразовании файла class. |
| ClassFile.DeadCodeOption | Параметр, определяющий, следует ли удалять недостижимый код при генерации карт стека. |
| ClassFile.DeadLabelsOption | Параметр, определяющий, следует ли фильтровать непривязанные метки и при возможности удалять содержащие их структуры. |
| ClassFile.DebugElementsOption | Параметр, определяющий, следует ли обрабатывать или отбрасывать отладочные PseudoInstruction при обходе CodeModel или CodeBuilder. |
| ClassFile.LineNumbersOption | Параметр, определяющий, следует ли обрабатывать или отбрасывать LineNumber при обходе CodeModel или CodeBuilder. |
| ClassFile.Option | Параметр, влияющий на анализ или запись файлов class. |
| ClassFile.ShortJumpsOption | Параметр, определяющий, следует ли при необходимости автоматически переписывать короткие переходы в эквивалентные инструкции. |
| ClassFile.StackMapsOption | Параметр, определяющий, следует ли генерировать карты стека. |
|
ClassFileBuilder<E extends ClassFileElement, B extends ClassFileBuilder<E, |
Построитель CompoundElement, принимающий элементы-члены, которые необходимо включить в создаваемую структуру. |
| ClassFileElement | Маркерный интерфейс для структур с особыми возможностями в формате файла
class. |
|
ClassFileTransform<C extends ClassFileTransform<C, |
Преобразование CompoundElement путём обработки отдельных элементов-членов и передачи результатов в ClassFileBuilder через ClassFileBuilder.transform(CompoundElement, ClassFileTransform). |
| ClassFileVersion | Моделирует младший и старший номера версии файла class (JVMS 4.1). |
| ClassHierarchyResolver | Предоставляет сведения об иерархии классов для генерации карт стека и проверки. |
| ClassHierarchyResolver.ClassHierarchyInfo | Информация о разрешённом классе. |
| ClassModel | Моделирует файл class. |
| ClassReader | Расширенные возможности чтения файлов class для AttributeMapper. |
| ClassSignature | Моделирует обобщённую сигнатуру класса или интерфейса, определённую в JVMS 4.7.9.1. |
| ClassTransform | Преобразование потоков ClassElement. |
| CodeBuilder | Построитель атрибутов Code (тел методов). |
| CodeBuilder.BlockCodeBuilder | Построитель блоков кода. |
| CodeBuilder.CatchBuilder | Построитель блоков catch. |
| CodeElement | Маркерный интерфейс для элемента-члена CodeModel. |
| CodeModel | Моделирует тело метода (атрибут Code). |
| CodeTransform | Преобразование потоков CodeElement. |
| CompoundElement<E extends ClassFileElement> | Структура файла class, которую можно рассматривать как композицию составляющих её структур-членов. |
| CustomAttribute<T extends CustomAttribute<T>> | Моделирует определённый пользователем атрибут в файле class. |
| FieldBuilder | Построитель полей. |
| FieldElement | Маркерный интерфейс для элемента-члена FieldModel. |
| FieldModel | Моделирует поле. |
| FieldTransform | Преобразование потоков FieldElement. |
| Instruction | Моделирует исполняемую инструкцию в массиве code атрибута Code метода. |
| Interfaces | Моделирует интерфейсы класса (JVMS 4.1). |
| Label | Маркер позиции среди инструкций тела метода. |
| MethodBuilder | Построитель методов. |
| MethodElement | Маркерный интерфейс для элемента-члена MethodModel. |
| MethodModel | Моделирует метод. |
| MethodSignature | Моделирует обобщённую сигнатуру метода или конструктора, определённую в JVMS 4.7.9.1. |
| MethodTransform | Преобразование потоков MethodElement. |
| Opcode | Описывает коды операций набора инструкций JVM, приведённые в JVMS 6.5. |
| Opcode.Kind | Виды кодов операций. |
| PseudoInstruction | |
| Signature | Моделирует сигнатуры обобщённых типов Java, определённые в JVMS 4.7.9.1. |
| Signature.ArrayTypeSig | Моделирует сигнатуру типа массива. |
| Signature.BaseTypeSig | Моделирует сигнатуру примитивного типа (JLS 4.2) или void. |
| Signature.ClassTypeSig | Моделирует сигнатуру типа класса или интерфейса, который может быть параметризованным. |
| Signature.RefTypeSig | Моделирует сигнатуру ссылочного типа, которым может быть класс, интерфейс, переменная типа или тип массива. |
| Signature.ThrowableSig | Маркерный интерфейс для сигнатуры типа, который может быть выброшен. |
| Signature.TypeArg | Моделирует аргумент типа — аргумент параметра типа. |
| Signature.TypeArg.Bounded | Моделирует аргумент типа с явно заданной границей типа. |
| Signature.TypeArg.Bounded.WildcardIndicator | Моделирует индикатор подстановочного знака аргумента типа. |
| Signature.TypeArg.Unbounded | Моделирует аргумент типа с неограниченным подстановочным знаком * или
? в программах Java. |
| Signature.TypeParam | Моделирует сигнатуру параметра типа обобщённого класса, интерфейса, метода или конструктора, который вводит переменную типа. |
| Signature.TypeVarSig | Моделирует сигнатуру переменной типа. |
| Superclass | Моделирует суперкласс класса (JVMS 4.1). |
| TypeAnnotation | Моделирует структуру type_annotation (JVMS 4.7.20). |
| TypeAnnotation.CatchTarget | Указывает, что аннотация размещена на типе с индексом i в объявлении параметра-исключения. |
| TypeAnnotation.EmptyTarget | Указывает, что аннотация размещена на типе в объявлении поля, возвращаемом типе метода, типе вновь созданного объекта либо типе получателя метода или конструктора. |
| TypeAnnotation.FormalParameterTarget | Указывает, что аннотация размещена на типе в объявлении формального параметра метода, конструктора или лямбда-выражения. |
| TypeAnnotation.LocalVarTarget | Указывает, что аннотация размещена на типе в объявлении локальной переменной, включая переменную, объявленную как ресурс в операторе try-with-resources. |
| TypeAnnotation.LocalVarTargetInfo | Указывает диапазон смещений в массиве кода, в пределах которого локальная переменная имеет значение, и индекс в массиве локальных переменных текущего кадра, по которому можно найти эту локальную переменную. |
| TypeAnnotation.OffsetTarget | Указывает, что аннотация размещена на типе в выражении instanceof или new либо на типе перед :: в выражении ссылки на метод. |
| TypeAnnotation.SupertypeTarget | Указывает, что аннотация размещена на типе в предложении extends или implements объявления класса или интерфейса. |
| TypeAnnotation.TargetInfo | Указывает, какой тип в объявлении или выражении аннотируется. |
| TypeAnnotation.TargetType | Вид цели, на которой размещена аннотация, как определено в JVMS 4.7.20.1. |
| TypeAnnotation.ThrowsTarget | Указывает, что аннотация размещена на типе с индексом i в предложении throws объявления метода или конструктора. |
| TypeAnnotation.TypeArgumentTarget | Указывает, что аннотация размещена либо на типе с индексом i в выражении приведения типа, либо на аргументе типа с индексом i в явном списке аргументов типа для любого из следующих элементов: выражения new, оператора явного вызова конструктора, выражения вызова метода или выражения ссылки на метод. |
| TypeAnnotation.TypeParameterBoundTarget | Указывает, что аннотация размещена на границе с индексом i параметра типа с индексом j в объявлении обобщённого класса, интерфейса, метода или конструктора. |
| TypeAnnotation.TypeParameterTarget | Указывает, что аннотация размещена в объявлении параметра типа с индексом i обобщённого класса, обобщённого интерфейса, обобщённого метода или обобщённого конструктора. |
| TypeAnnotation.TypePathComponent | JVMS: структура Type_path определяет, какая часть типа аннотируется, как указано в JVMS 4.7.20.2
|
| TypeAnnotation.TypePathComponent.Kind | Вид пути к типу, как определено в JVMS 4.7.20.2
|
| TypeKind | Описывает типы данных, с которыми работает виртуальная машина Java. |
© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/classfile/package-summary.html