📅 2026-05-19 👤 郑凯 🏷️ KSP · 编译器插件 · Kotlin

Kotlin 编译器插件:KSP 与编译器前端扩展机制深度

从 KSP 架构、Symbol Processor 生命周期、KSVisitor 访问者模式出发,解析 Kotlin 编译插件的扩展机制,以及实战中的最佳实践。

1. KSP 架构与定位

1.1 KSP vs KAPT

Kotlin Symbol Processing(KSP)是 Kotlin 官方推出的注解处理器替代方案。相比 KAPT(Kotlin Annotation Processing Tool),KSP 直接操作 Kotlin 编译器前端的符号表,不需要启动 JVM 反射或 Java 注解处理:

维度 KAPT KSP
架构 Java APT + JVM 反射 直接访问 Kotlin 符号
依赖 kotlin-reflect(JVM) 无反射,直接符号查询
速度 慢(启动开销大) 快(2-3x vs KAPT)
KMP 支持 ❌ 仅 JVM ✅ 全部平台
增量编译 有限支持 完整增量支持

1.2 KSP 编译流程

Kotlin Source
Kotlin Compiler Frontend
KSVisitor / Symbol
Generated Code
KSP 工作流程

2. Symbol Processor 生命周期

2.1 SymbolProcessor 接口

KSP 的核心是 SymbolProcessor 接口,所有处理器都实现这个接口:

SymbolProcessor 接口
interface SymbolProcessor {
    // 处理函数:在所有符号解析完成后调用
    // resolver.getSymbolsWithAnnotation() 返回的符号在这里处理
    fun process(resolver: Resolver): List<KSNode>

    // finish:在编译结束时调用,用于生成文件
    fun finish()

    // onError:处理过程中遇到错误时调用
    fun onError(error: KSError)
}

// 创建处理器
class MyProcessor : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSNode> {
        // 获取所有标记了 @MyAnnotation 的符号
        val symbols = resolver.getSymbolsWithAnnotation("com.example.MyAnnotation")
        symbols.filter { it is KSClassDeclaration }
            .forEach { processClass(it) }
        return emptyList()
    }
}

2.2 Resolver 与符号查询

Resolver 是 KSP 中最重要的对象,它提供了对编译上下文中所有符号的查询能力:

Resolver 查询 API
// Resolver 提供的查询方法
// 1. 按注解查询
Resolvers.getSymbolsWithAnnotation("com.example.MyAnnotation")

// 2. 按包名查询所有类
Resolver.getClassDeclarationsInPackage("com.example")

// 3. 查找类型引用
Resolver.getTypeArguments(typeRef: KSTypeReference)

// 4. 扩展函数:获取直接子类型
Resolver.getDirectSubclasses(ksClass: KSClassDeclaration)

// 5. 创建类型 alias
Resolver.createKSType(javaType: Type)

2.3 处理器执行顺序

KSP 通过 processorPath 确定处理器执行顺序。使用 @AutoCanonical@Order 控制:

处理器顺序配置
// build.gradle.kts
 kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xplugin=" +
            "$kspPluginJar${File.pathSeparator}$otherPluginJar")
        freeCompilerArgs.add("-Xallow.any.target.in.source.root")
    }
}

// 代码中指定顺序
@AutoCanonical
class FirstProcessor : SymbolProcessor { ... }

@AutoCanonical
class SecondProcessor : SymbolProcessor { ... }
// FirstProcessor 会在 SecondProcessor 之前执行

3. KSVisitor 访问者模式

3.1 KSVisitor 接口层次

KSP 使用 Visitor 模式遍历 AST。所有的 Visitor 接口都继承自 KSVisitor<T, R>

KSVisitor 接口定义
// KSVisitor<T, R> 定义
// T: 被访问的节点类型(如 KSClassDeclaration)
// R: visit 方法的返回值类型
interface KSVisitor<T, R> {
    fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: T): R
    fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: T): R
    fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: T): R
    fun visitAnnotation(annotation: KSAnnotation, data: T): R
    // ... 其他 visit 方法
}

// 具体 Visitor 实现示例
class MyVisitor : KSVisitorVoid {
    override fun visitClassDeclaration(decl: KSClassDeclaration, data: Unit) {
        println("Found class: ${decl.simpleName}")
        decl.getDeclaredProperties().forEach { it.accept(this, Unit) }
        decl.getDeclaredFunctions().forEach { it.accept(this, Unit) }
    }
}

3.2 常用 KSNode 类型

KSP 中的符号节点类型:

类型 说明 可获取信息
KSClassDeclaration 类/接口/对象声明 名称、父类、成员
KSFunctionDeclaration 函数声明 参数、返回值、注解
KSPropertyDeclaration 属性声明 类型、初始值、getter/setter
KSAnnotation 注解 注解类型、参数值
KSTypeReference 类型引用 解析后的类型

4. 编译插件扩展机制

4.1 Kotlin Compiler Plugin 体系

Kotlin 编译器支持三种扩展点:

Kotlin 编译器扩展点
// 1. IR 变换(IR Plugin)
// 插入到 Kotlin → IR → Bytecode 流水线
// Compose Compiler 就是 IR Plugin

// 2. 编译检查(Check Plugin)
// 在编译器前端执行类型检查
// 可以添加自定义警告/错误

// 3. KSP(Symbol Processor)
// 在编译前期处理注解和符号
// 生成代码或修改 AST

// 编译插件注册(plugin.yml)
// - name: org.jetbrains.kotlin.compose.compilerplugins
//   embeddedIntoCompiler: true
//   changesCompilation: true

4.2 编写自定义 KSP 处理器

实战:实现一个生成 equals/hashCode/toString 的处理器:

自定义 KSP 处理器示例
// 1. 定义注解
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class AutoData

// 2. 实现 SymbolProcessor
class AutoDataProcessor : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSNode> {
        val symbols = resolver.getSymbolsWithAnnotation(AutoData::class.qualifiedName)
        symbols.filter { it is KSClassDeclaration }
            .forEach { generateDataClass(it) }
        return symbols.toList()
    }

    private fun generateDataClass(decl: KSClassDeclaration) {
        // 生成代码...
        KSFile.createKSFile(...).appendText(generatedCode)
    }
}

// 3. 注册服务
// META-INF/services/com.google.devtools.ksp.SymbolProcessor
// com.example.AutoDataProcessor

5. 实战与最佳实践

5.1 KSP 性能优化

KSP 相比 KAPT 更快,但仍有优化空间:

✅ KSP 性能建议

  • 使用 resolver.getSymbolsWithAnnotation() 而非全量扫描
  • 增量处理:利用 KSNode.containingFile 确定变更范围
  • 避免深度递归遍历,优先使用 Visitor
  • 缓存已解析的类型引用

❌ 常见性能问题

  • 在 process() 中调用 resolve() 触发完整类型解析
  • 全量扫描所有类声明(大量 I/O)
  • 生成过多文件导致增量编译失效
  • 不使用 @GeneratedAnnotation 丢失增量标记

5.2 实际案例:Room KSP 迁移

💡 实战案例

Google 的 Room 数据库从 KAPT 迁移到 KSP 后,编译速度提升约 2-3x,增量编译支持也大幅改善。以下是关键迁移步骤:

  • kapt 插件替换为 ksp
  • 处理器从 Java 反射改为 KSP Symbol API
  • 使用 KSClassDeclaration.getValueParameters() 获取构造参数
  • 使用 KSTypeReference.resolve() 获取完整类型
KSP 迁移检查清单
# KSP 迁移评估
# □ 确认所有依赖库都有 KSP 版本
# □ 处理器不依赖 JVM 反射
# □ 检查 KSVisitor 是否覆盖所有节点类型
# □ 添加 @GeneratedAnnotation 支持增量
# □ 测试增量编译场景
# □ 验证所有平台的生成代码一致性