Spec-Zone.ru › Kotlin 2

Библиотека Kotlin Metadata JVM

Библиотека kotlin-metadata-jvm предоставляет инструменты для чтения, изменения и создания метаданных классов Kotlin, скомпилированных для JVM. Эти метаданные хранятся в аннотации @Metadata в файлах .class и используются такими библиотеками и инструментами, как kotlin-reflect, для анализа во время выполнения специфичных для Kotlin конструкций, таких как свойства, функции и классы.

Библиотека kotlin-reflect использует метаданные для получения сведений о классах Kotlin во время выполнения. Любые несоответствия между метаданными и фактическим файлом .class могут привести к некорректной работе при использовании рефлексии.

Библиотеку Kotlin Metadata JVM также можно использовать для анализа различных атрибутов объявлений, таких как видимость или модальность, а также для создания и встраивания метаданных в файлы .class.

Добавление библиотеки в проект

Чтобы подключить библиотеку Kotlin Metadata JVM к проекту, добавьте соответствующую конфигурацию зависимостей с учетом используемого инструмента сборки.

Библиотека Kotlin Metadata JVM использует ту же схему версионирования, что и компилятор Kotlin и стандартная библиотека. Убедитесь, что используемая вами версия соответствует версии Kotlin в проекте.

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, которые полностью поддерживаются вашим проектом.

    Функция readStrict() поддерживает форматы метаданных вплоть до версии на одну выше JvmMetadataVersion.LATEST_STABLE_SUPPORTED, соответствующей последней версии Kotlin, используемой в проекте. Например, если проект зависит от kotlin-metadata-jvm:2.1.0, функция readStrict() может обрабатывать метаданные вплоть до Kotlin 2.2.x; в противном случае она выдает ошибку, чтобы предотвратить некорректную обработку неизвестных форматов.

    Дополнительные сведения см. в репозитории Kotlin Metadata на GitHub.

При разборе метаданных экземпляр 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 2.4.0, Kotlin хранит аннотации в метаданных Kotlin. Если вы анализируете файлы классов, скомпилированные более ранними версиями, аннотаций в метаданных не будет.

Изменяя аннотации в метаданных, следите за тем, чтобы они оставались согласованными с аннотациями, хранящимися в байт-коде. Если они рассинхронизируются, инструменты, использующие рефлексию или анализ байт-кода, могут выдавать результаты, отличающиеся от результатов инструментов, читающих метаданные Kotlin.

Библиотека kotlin-metadata-jvm предоставляет следующие API для доступа к аннотациям:

  • KmClass.annotations

  • KmFunction.annotations

  • KmProperty.annotations

  • KmConstructor.annotations

  • KmPropertyAccessorAttributes.annotations

  • KmValueParameter.annotations

  • KmFunction.extensionReceiverAnnotations

  • KmProperty.extensionReceiverAnnotations

  • KmProperty.backingFieldAnnotations

  • KmProperty.delegateFieldAnnotations

  • KmEnumEntry.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.

Для этого выполните следующие действия:

  1. Прочитайте байт-код файла .class с помощью класса ClassReader из библиотеки ASM. Этот класс обрабатывает скомпилированный файл и заполняет объект ClassNode, представляющий структуру класса.

  2. Извлеките @Metadata из объекта ClassNode. В примере ниже для этого используется пользовательская функция-расширение findAnnotation().

  3. Выполните разбор извлеченных метаданных с помощью функции KotlinClassMetadata.readLenient().

  4. Изучите разобранные метаданные с помощью свойств 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:

  1. Разберите метаданные с помощью функции readStrict(), чтобы загрузить аннотацию @Metadata в структурированный объект KotlinClassMetadata.

  2. Внесите изменения в метаданные, например отфильтруйте функции или измените атрибуты непосредственно в kmClass или других структурах метаданных.

  3. Используйте функцию 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()
    }
}

Вместо отдельных вызовов readStrict() и write() можно использовать функцию transform(). Эта функция разбирает метаданные, применяет преобразования с помощью лямбда-выражения и автоматически записывает измененные метаданные.

Создание метаданных с нуля

Чтобы создать метаданные для файла класса Kotlin с нуля с помощью библиотеки Kotlin Metadata JVM:

  1. Создайте экземпляр KmClass, KmPackage или KmLambda в зависимости от типа метаданных, которые требуется сгенерировать.

  2. Добавьте к экземпляру атрибуты, например имя класса, видимость, конструкторы и сигнатуры функций.

    Для сокращения шаблонного кода при задании свойств можно использовать apply() функцию области видимости.

  3. Используйте этот экземпляр для создания объекта KotlinClassMetadata, который может генерировать аннотацию @Metadata.

  4. Укажите версию метаданных, например JvmMetadataVersion.LATEST_STABLE_SUPPORTED, и задайте флаги (0, если флаги отсутствуют, или скопируйте флаги из существующих файлов, если это необходимо).

  5. Используйте класс 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.")
}

Более подробный пример см. в репозитории Kotlin Metadata JVM на GitHub.

Что дальше?

  • См. справочник API библиотеки Kotlin Metadata JVM.

  • Посетите репозиторий Kotlin Metadata JVM на GitHub.

  • Узнайте о метаданных модулей и работе с файлами .kotlin_module.

28 апреля 2026 г.
Создание и использование сериализаторовОбзор

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API