前置知识: Kotlin

Kotlin与编译器插件

00:00
4 min Advanced 2026/6/14

kapt、KSP与编译器插件

概述

Kotlin 编译器插件是在编译期间对代码进行自动处理的工具。它们可以生成代码、修改已有代码、或执行额外的检查。最常见的用途是注解处理——扫描代码中的注解,然后自动生成额外的源文件。Kotlin 生态中有三种主要的注解处理方式:kapt、KSP 和编译器插件。

理解编译器插件的工作原理,有助于你选择合适的工具,并理解项目中自动生成的代码从何而来。

基础概念

  • kapt(Kotlin Annotation Processing Tool):Kotlin 早期的注解处理方案,通过将 Kotlin 代码转换为 Java 存根(stub),然后交给 Java 的 APT 处理。兼容性好但速度慢
  • KSP(Kotlin Symbol Processing):Kotlin 新一代的符号处理 API,直接操作 Kotlin 的 AST,速度比 kapt 快 2 倍以上
  • Compiler Plugin:直接修改编译器行为的插件,如 all-open、no-arg、serialization 等
  • 注解处理:在编译期扫描注解,自动生成代码的过程
  • 代码生成:根据注解或配置,自动生成 Kotlin/Java 源文件

快速上手

kapt 的基本使用

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
    // 添加 kapt 插件
    kotlin("kapt") version "2.0.0"
}

dependencies {
    // 使用 kapt 处理注解
    kapt("com.google.dagger:hilt-compiler:2.50")
    implementation("com.google.dagger:hilt-android:2.50")
}

KSP 的基本使用

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
    // 添加 KSP 插件
    id("com.google.devtools.ksp") version "2.0.0-1.0.21"
}

dependencies {
    // 使用 ksp 处理注解(注意是 ksp 而不是 kapt)
    ksp("com.example:processor:1.0.0")
    implementation("com.example:annotations:1.0.0")
}

KSP 比 kapt 更快,因为直接操作 Kotlin AST,不需要生成 Java 存根。

详细用法

内置编译器插件

Kotlin 自带几个常用的编译器插件:

all-open 插件

标记了指定注解不再默认 final,可以被继承

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
    kotlin("plugin.allopen") version "2.0.0"
}

// 配置哪些注解的类需要打开
allOpen {
    annotation("com.example.OpenClass")
    annotation("javax.persistence.Entity")  // JPA 实体类需要被代理继承
}

// 使用自定义注解
@Target(AnnotationTarget.CLASS)
annotation class OpenClass

@OpenClass
class MyService {
    // 没有 all-open 插件时,Kotlin 类默认是 final,不能被继承
    // 有了 all-open,标记了 @OpenClass 的类会被编译为 open class
}

no-arg 插件

标记了指定注解自动生成无参构造器

// build.gradle.kts
plugins {
    kotlin("plugin.noarg") version "2.0.0"
}

noArg {
    annotation("com.example.NoArg")
    annotation("javax.persistence.Entity")
}

@Target(AnnotationTarget.CLASS)
annotation class NoArg

@NoArg
data class User(val name: String, val age: Int)
// 编译后会自动生成无参构造器,某些框架(如 JPA)需要

serialization 插件

标记了 @Serializable 的自动生成序列化代码:

// build.gradle.kts
plugins {
    kotlin("plugin.serialization") version "2.0.0"
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

// 使用
import kotlinx.serialization.*

@Serializable
data class User(val name: String, val age: Int)
// 编译器插件会自动生成 User.serializer()

kapt 与 KSP 的选择

// 场景一:使用 Room 数据库
// kapt 方式
dependencies {
    kapt("androidx.room:room-compiler:2.6.0")
}

// KSP 方式(推荐,更快)
dependencies {
    ksp("androidx.room:room-compiler:2.6.0")
}

// 场景二:使用 Dagger/Hilt
// kapt 方式(目前 Hilt 主要支持 kapt)
dependencies {
    kapt("com.google.dagger:hilt-compiler:2.50")
}

// 场景三:使用 Moshi
// kapt 方式
dependencies {
    kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")
}

// KSP 方式
dependencies {
    ksp("dev.zacsweers.moshix:moshi-kotlin-codegen:0.25.0")
}

自定义 KSP 处理器

// 定义注解
@Target(AnnotationTarget.CLASS)
annotation class Factory

// 实现 SymbolProcessor
class FactoryProcessor : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSAnnotated> {
        // 查找所有标记了 @Factory 的类
        val factories = resolver.getSymbolsWithAnnotation(Factory::class.qualifiedName!!)
        factories.forEach { annotated ->
            val classDecl = annotated as KSClassDeclaration
            // 生成工厂类代码
            generateFactory(classDecl)
        }
        return emptyList()
    }

    private fun generateFactory(classDecl: KSClassDeclaration) {
        val className = classDecl.simpleName.asString()
        val packageName = classDecl.packageName.asString()
        // 使用 CodeGenerator 生成代码
        val file = FileSpec.builder("$packageName.factory", "${className}Factory")
            .addFunction(FunSpec.builder("create")
                .returns(ClassName(packageName, className))
                .addStatement("return $className()")
                .build())
            .build()
        // 写入文件
    }
}

// 注册处理器
class FactoryProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return FactoryProcessor()
    }
}

常见场景

Spring Boot 项目中的插件配置

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.0"
    kotlin("plugin.spring") version "2.0.0"    // 包含 all-open
    kotlin("plugin.jpa") version "2.0.0"       // 包含 all-open + no-arg
    kotlin("plugin.serialization") version "2.0.0"
}

// kotlin-spring 插件自动为以下注解的类打开:
// @Component, @Service, @Repository, @Controller, @Configuration 等

// kotlin-jpa 插件自动为以下注解的类打开并生成无参构造器:
// @Entity, @Embeddable, @MappedSuperclass 等

Android 项目中的插件配置

// build.gradle.kts
plugins {
    kotlin("android") version "2.0.0"
    kotlin("kapt") version "2.0.0"              // 或使用 KSP
    kotlin("plugin.serialization") version "2.0.0"
    id("com.google.devtools.ksp") version "2.0.0-1.0.21"
}

dependencies {
    // Room 数据库(推荐用 KSP)
    ksp("androidx.room:room-compiler:2.6.0")

    // Hilt 依赖注入(目前仍需 kapt)
    kapt("com.google.dagger:hilt-compiler:2.50")

    // 序列化
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

Lombok 与 Kotlin

// 如果项目同时使用 Lombok 和 Kotlin
plugins {
    kotlin("jvm") version "2.0.0"
    kotlin("kapt") version "2.0.0"
    id("io.freefair.lombok") version "8.4"
}

// 注意:Kotlin 代码不能直接使用 Lombok 注解
// Lombok 只能处理 Java 代码
// 如果 Kotlin 需要调用 Lombok 生成的代码,需要使用 lombok-config 配置

注意事项

  • KSP 是未来方向:Google 推荐使用 KSP 替代 kapt,新项目应优先选择 KSP
  • kapt 的编译较慢:kapt 需要生成 Java 存增加编译时
  • KSP 的兼容性:不是所有注解处理器都支持 KSP,使用前需要确认
  • 编译器插件顺序:如果同时使用编译器插件,注意它们的顺序可能影响编译结果
  • 生成的代码不可见注解处理器生成的代码通常在 build 目录下,IDE 可能需要手动标记源码目录
  • 增量编译:KSP 支持增量编译,kapt 也支持效果不如 KSP

进阶用法

kapt 的配置选项

// build.gradle.kts
kapt {
    // 启用增量编译
    useBuildCache = true

    // 指定注解处理器的参数
    arguments {
        arg("option1", "value1")
        arg("room.schemaLocation", "$projectDir/schemas")
    }

    // 排除不需要处理的依赖
    exclude(group = "com.example", module = "unused-module")
}

KSP 的配置选项

// build.gradle.kts
ksp {
    // 传递参数给处理器
    arg("option1", "value1")
    arg("room.schemaLocation", "$projectDir/schemas")

    // 排除特定处理器
    excludeProcessor("com.example.SomeProcessor")
}

// KSP 支持多轮处理
// 如果处理器生成了新的带注解的代码,KSP 会自动进行下一轮处理

自定义编译器插件

需求,可以编写自己的 Kotlin 编译器插件:

// 编写编译器插件需要实现 ComponentRegistrar 接口
// 这属于非常高级的用法,通常只在框架开发中使用

// 示例:一个简单的编译器插件结构
class MyPluginComponentRegistrar : ComponentRegistrar {
    override fun registerProjectComponents(
        project: MockProject,
        configuration: CompilerConfiguration
    ) {
        // 注册 IrGenerationExtension 或 ClassBuilderInterceptorExtension
        // 来修改编译过程中的代码
    }
}

// 在 META-INF 中注册插件
// org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
// com.example.MyPluginComponentRegistrar

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式