Библиотека Kotlin Metadata JVM
Библиотека kotlin-metadata-jvm предоставляет инструменты для чтения, изменения и создания метаданных классов Kotlin, скомпилированных для JVM. Эти метаданные хранятся в аннотации @Metadata в файлах .class и используются такими библиотеками и инструментами, как kotlin-reflect, для анализа во время выполнения специфичных для Kotlin конструкций, таких как свойства, функции и классы.
Библиотеку Kotlin Metadata JVM также можно использовать для анализа различных атрибутов объявлений, таких как видимость или модальность, а также для создания и встраивания метаданных в файлы .class.
Добавление библиотеки в проект
Чтобы подключить библиотеку Kotlin Metadata JVM к проекту, добавьте соответствующую конфигурацию зависимостей с учетом используемого инструмента сборки.
Gradle
Добавьте следующую зависимость в файл build.gradle(.kts):
// build.gradle.kts
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-metadata-jvm:2.4.20")
}
// build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-metadata-jvm:2.4.20'
}
Maven
Добавьте следующую зависимость в файл pom.xml.
<project>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-metadata-jvm</artifactId>
<version>2.4.20</version>
</dependency>
</dependencies>
...
</project>
Чтение и разбор метаданных
Библиотека kotlin-metadata-jvm извлекает структурированную информацию из скомпилированных файлов .class Kotlin, например имена классов, видимость и сигнатуры. Ее можно использовать в проектах, которым требуется анализировать скомпилированные объявления Kotlin. Например, Binary Compatibility Validator (BCV) использует kotlin-metadata-jvm для вывода объявлений общедоступного API.
Начать изучение метаданных классов Kotlin можно, получив аннотацию @Metadata скомпилированного класса с помощью рефлексии:
fun main() {
// Specifies the fully qualified name of the class
val clazz = Class.forName("org.example.SampleClass")
// Retrieves the @Metadata annotation
val metadata = clazz.getAnnotation(Metadata::class.java)
// Checks if the metadata is present
if (metadata != null) {
println("This is a Kotlin class with metadata.")
} else {
println("This is not a Kotlin class.")
}
}
Получив аннотацию @Metadata, используйте функцию readLenient() или readStrict() из API KotlinClassMetadata, чтобы выполнить ее разбор. Эти функции извлекают подробные сведения о классах или файлах с учетом различных требований к совместимости:
readLenient(): используйте эту функцию для чтения метаданных, в том числе созданных более новыми версиями компилятора Kotlin. Эта функция не поддерживает изменение или запись метаданных.-
readStrict(): используйте эту функцию, если требуется изменить и записать метаданные. ФункцияreadStrict()работает только с метаданными, созданными версиями компилятора Kotlin, которые полностью поддерживаются вашим проектом.
При разборе метаданных экземпляр KotlinClassMetadata предоставляет структурированную информацию об объявлениях уровня класса или файла. Для классов используйте свойство kmClass, чтобы проанализировать подробные метаданные уровня класса, например имя класса, функции, свойства и атрибуты, такие как видимость. Для объявлений уровня файла метаданные представлены свойством kmPackage, которое содержит функции и свойства верхнего уровня из фасадов файлов, созданных компилятором Kotlin.
В следующем примере кода показано, как использовать readLenient() для разбора метаданных, анализа сведений уровня класса с помощью kmClass и получения объявлений уровня файла с помощью kmPackage:
// Imports the necessary libraries
import kotlin.metadata.jvm.*
import kotlin.metadata.*
fun main() {
// Specifies the fully qualified class name
val className = "org.example.SampleClass"
try {
// Retrieves the class object for the specified name
val clazz = Class.forName(className)
// Retrieves the @Metadata annotation
val metadataAnnotation = clazz.getAnnotation(Metadata::class.java)
if (metadataAnnotation != null) {
println("Kotlin Metadata found for class: $className")
// Parses metadata using the readLenient() function
val metadata = KotlinClassMetadata.readLenient(metadataAnnotation)
when (metadata) {
is KotlinClassMetadata.Class -> {
val kmClass = metadata.kmClass
println("Class name: ${kmClass.name}")
// Iterates over functions and checks visibility
kmClass.functions.forEach { function ->
val visibility = function.visibility
println("Function: ${function.name}, Visibility: $visibility")
}
}
is KotlinClassMetadata.FileFacade -> {
val kmPackage = metadata.kmPackage
// Iterates over functions and checks visibility
kmPackage.functions.forEach { function ->
val visibility = function.visibility
println("Function: ${function.name}, Visibility: $visibility")
}
}
else -> {
println("Unsupported metadata type: $metadata")
}
}
} else {
println("No Kotlin Metadata found for class: $className")
}
} catch (e: ClassNotFoundException) {
println("Class not found: $className")
} catch (e: Exception) {
println("Error processing metadata: ${e.message}")
e.printStackTrace()
}
}
Запись и чтение аннотаций в метаданных
Kotlin хранит аннотации как в байт-коде, так и в метаданных Kotlin. Если для чтения или записи аннотаций вы используете библиотеку kotlin-metadata-jvm, то работаете с их представлением в метаданных.
Изменяя аннотации в метаданных, следите за тем, чтобы они оставались согласованными с аннотациями, хранящимися в байт-коде. Если они рассинхронизируются, инструменты, использующие рефлексию или анализ байт-кода, могут выдавать результаты, отличающиеся от результатов инструментов, читающих метаданные Kotlin.
Библиотека kotlin-metadata-jvm предоставляет следующие API для доступа к аннотациям:
KmClass.annotationsKmFunction.annotationsKmProperty.annotationsKmConstructor.annotationsKmPropertyAccessorAttributes.annotationsKmValueParameter.annotationsKmFunction.extensionReceiverAnnotationsKmProperty.extensionReceiverAnnotationsKmProperty.backingFieldAnnotationsKmProperty.delegateFieldAnnotationsKmEnumEntry.annotations
Пример чтения аннотаций из метаданных Kotlin:
import kotlin.metadata.ExperimentalAnnotationsInMetadata
import kotlin.metadata.jvm.KotlinClassMetadata
annotation class Label(val value: String)
@Label("Message class")
class Message
fun main() {
val metadata = Message::class.java.getAnnotation(Metadata::class.java)
val kmClass = (KotlinClassMetadata.readStrict(metadata) as KotlinClassMetadata.Class).kmClass
println(kmClass.annotations)
// [@Label(value = StringValue("Message class"))]
}
Извлечение метаданных из байт-кода
Хотя метаданные можно получить с помощью рефлексии, другой способ — извлечь их из байт-кода с помощью фреймворка для работы с байт-кодом, например ASM.
Для этого выполните следующие действия:
Прочитайте байт-код файла
.classс помощью классаClassReaderиз библиотеки ASM. Этот класс обрабатывает скомпилированный файл и заполняет объектClassNode, представляющий структуру класса.Извлеките
@Metadataиз объектаClassNode. В примере ниже для этого используется пользовательская функция-расширениеfindAnnotation().Выполните разбор извлеченных метаданных с помощью функции
KotlinClassMetadata.readLenient().Изучите разобранные метаданные с помощью свойств
kmClassиkmPackage.
Пример:
// Imports the necessary libraries
import kotlin.metadata.jvm.*
import kotlin.metadata.*
import org.objectweb.asm.*
import org.objectweb.asm.tree.*
import java.io.File
// Checks if an annotation refers to a specific name
fun AnnotationNode.refersToName(name: String) =
desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length)
// Retrieves annotation values by key
private fun List<Any>.annotationValue(key: String): Any? {
for (index in (0 until size / 2)) {
if (this[index * 2] == key) {
return this[index * 2 + 1]
}
}
return null
}
// Defines a custom extension function to locate an annotation by its name in a ClassNode
fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false): AnnotationNode? {
val visible = visibleAnnotations?.firstOrNull { it.refersToName(annotationName) }
if (!includeInvisible) return visible
return visible ?: invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) }
}
// Operator to simplify retrieving annotation values
operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key)
// Extracts Kotlin metadata from a class node
fun ClassNode.readMetadataLenient(): KotlinClassMetadata? {
val metadataAnnotation = findAnnotation("kotlin/Metadata", false) ?: return null
@Suppress("UNCHECKED_CAST")
val metadata = Metadata(
kind = metadataAnnotation["k"] as Int?,
metadataVersion = (metadataAnnotation["mv"] as List<Int>?)?.toIntArray(),
data1 = (metadataAnnotation["d1"] as List<String>?)?.toTypedArray(),
data2 = (metadataAnnotation["d2"] as List<String>?)?.toTypedArray(),
extraString = metadataAnnotation["xs"] as String?,
packageName = metadataAnnotation["pn"] as String?,
extraInt = metadataAnnotation["xi"] as Int?
)
return KotlinClassMetadata.readLenient(metadata)
}
// Converts a file to a ClassNode for bytecode inspection
fun File.toClassNode(): ClassNode {
val node = ClassNode()
this.inputStream().use { ClassReader(it).accept(node, ClassReader.SKIP_CODE) }
return node
}
fun main() {
val classFilePath = "build/classes/kotlin/main/org/example/SampleClass.class"
val classFile = File(classFilePath)
// Reads the bytecode and processes it into a ClassNode object
val classNode = classFile.toClassNode()
// Locates the @Metadata annotation and reads it leniently
val metadata = classNode.readMetadataLenient()
if (metadata != null && metadata is KotlinClassMetadata.Class) {
// Inspects the parsed metadata
val kmClass = metadata.kmClass
// Prints class details
println("Class name: ${kmClass.name}")
println("Functions:")
kmClass.functions.forEach { function ->
println("- ${function.name}, Visibility: ${function.visibility}")
}
}
}
Изменение метаданных
При использовании таких инструментов, как ProGuard, для уменьшения размера и оптимизации байт-кода некоторые объявления могут удаляться из файлов .class. ProGuard автоматически обновляет метаданные, чтобы они соответствовали измененному байт-коду.
Однако если вы разрабатываете собственный инструмент, который аналогичным образом изменяет байт-код Kotlin, необходимо обеспечить соответствующую корректировку метаданных. С помощью библиотеки kotlin-metadata-jvm можно обновлять объявления, изменять атрибуты и удалять определенные элементы.
Например, если вы используете инструмент для JVM, удаляющий закрытые методы из файлов классов Java, для сохранения согласованности необходимо также удалить закрытые функции из метаданных Kotlin:
Разберите метаданные с помощью функции
readStrict(), чтобы загрузить аннотацию@Metadataв структурированный объектKotlinClassMetadata.Внесите изменения в метаданные, например отфильтруйте функции или измените атрибуты непосредственно в
kmClassили других структурах метаданных.Используйте функцию
write(), чтобы закодировать измененные метаданные в новую аннотацию@Metadata.
Пример удаления закрытых функций из метаданных класса:
// Imports the necessary libraries
import kotlin.metadata.jvm.*
import kotlin.metadata.*
fun main() {
// Specifies the fully qualified class name
val className = "org.example.SampleClass"
try {
// Retrieves the class object for the specified name
val clazz = Class.forName(className)
// Retrieves the @Metadata annotation
val metadataAnnotation = clazz.getAnnotation(Metadata::class.java)
if (metadataAnnotation != null) {
println("Kotlin Metadata found for class: $className")
// Parses metadata using the readStrict() function
val metadata = KotlinClassMetadata.readStrict(metadataAnnotation)
if (metadata is KotlinClassMetadata.Class) {
val kmClass = metadata.kmClass
// Removes private functions from the class metadata
kmClass.functions.removeIf { it.visibility == Visibility.PRIVATE }
println("Removed private functions. Remaining functions: ${kmClass.functions.map { it.name }}")
// Serializes the modified metadata back
val newMetadata = metadata.write()
// After modifying the metadata, you need to write it into the class file
// To do so, you can use a bytecode manipulation framework such as ASM
println("Modified metadata: ${newMetadata}")
} else {
println("The metadata is not a class.")
}
} else {
println("No Kotlin Metadata found for class: $className")
}
} catch (e: ClassNotFoundException) {
println("Class not found: $className")
} catch (e: Exception) {
println("Error processing metadata: ${e.message}")
e.printStackTrace()
}
}
Создание метаданных с нуля
Чтобы создать метаданные для файла класса Kotlin с нуля с помощью библиотеки Kotlin Metadata JVM:
Создайте экземпляр
KmClass,KmPackageилиKmLambdaв зависимости от типа метаданных, которые требуется сгенерировать.-
Добавьте к экземпляру атрибуты, например имя класса, видимость, конструкторы и сигнатуры функций.
Используйте этот экземпляр для создания объекта
KotlinClassMetadata, который может генерировать аннотацию@Metadata.Укажите версию метаданных, например
JvmMetadataVersion.LATEST_STABLE_SUPPORTED, и задайте флаги (0, если флаги отсутствуют, или скопируйте флаги из существующих файлов, если это необходимо).Используйте класс
ClassWriterиз ASM, чтобы встроить поля метаданных, такие какkind,data1иdata2, в файл.class.
В следующем примере показано, как создать метаданные для простого класса Kotlin:
// Imports the necessary libraries
import kotlin.metadata.*
import kotlin.metadata.jvm.*
import org.objectweb.asm.*
fun main() {
// Creates a KmClass instance
val klass = KmClass().apply {
name = "Hello"
visibility = Visibility.PUBLIC
constructors += KmConstructor().apply {
visibility = Visibility.PUBLIC
signature = JvmMethodSignature("<init>", "()V")
}
functions += KmFunction("hello").apply {
visibility = Visibility.PUBLIC
returnType = KmType().apply {
classifier = KmClassifier.Class("kotlin/String")
}
signature = JvmMethodSignature("hello", "()Ljava/lang/String;")
}
}
// Serializes a KotlinClassMetadata.Class instance, including the version and flags, into a @kotlin.Metadata annotation
val annotationData = KotlinClassMetadata.Class(
klass, JvmMetadataVersion.LATEST_STABLE_SUPPORTED, 0
).write()
// Generates a .class file with ASM
val classBytes = ClassWriter(0).apply {
visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Hello", null, "java/lang/Object", null)
// Writes @kotlin.Metadata instance to the .class file
visitAnnotation("Lkotlin/Metadata;", true).apply {
visit("mv", annotationData.metadataVersion)
visit("k", annotationData.kind)
visitArray("d1").apply {
annotationData.data1.forEach { visit(null, it) }
visitEnd()
}
visitArray("d2").apply {
annotationData.data2.forEach { visit(null, it) }
visitEnd()
}
visitEnd()
}
visitEnd()
}.toByteArray()
// Writes the generated .class file to disk
java.io.File("Hello.class").writeBytes(classBytes)
println("Metadata and .class file created successfully.")
}
Что дальше?
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/metadata-jvm.html