Kotlin 符号处理(KSP)是一种 API,您可以用它来开发轻量级编译器插件。KSP 提供了一个简化的编译器插件 API,可充分利用 Kotlin 的强大功能,同时将学习曲线保持在最低水平。与 kapt 相比,使用 KSP 的注释处理器运行速度最多可提高两倍。
KSP
What is KSP
借用官网的内容
Kotlin 符号处理(KSP)是一种 API,您可以用它来开发轻量级编译器插件。KSP 提供了一个简化的编译器插件 API,可充分利用 Kotlin 的强大功能,同时将学习曲线保持在最低水平。与 kapt 相比,使用 KSP 的注释处理器运行速度最多可提高两倍。
直白的意思就是说,可以读取 Kotlin 文件中的注释,在编译过程中对注解和其注解的内容进行处理的一个框架。
常见的使用方式如使用注解处理器搭配 KPoet生成模板代码(如 ButterKnife、Room),或者使用注解处理器搭配 ASM 或 KASM 在编译期进行字节码插桩以实现面向切面编程。
Why KSP
简单的说,对比 kapt 而言 KSP 的性能更高,不依赖于 JVM 平台,API 设计以 Kotlin 的语法为目标,更易使用也更加容易理解。
kapt 将 Kotlin 代码编译成 Java 存根,这些存根保留了 Java 注释处理器关心的信息,但也拖慢了速度。生成存根的成本大约是完整 kotlinc 分析的 1/3,这个过程可能比很多注解处理器耗费的时间还长。
Quick Start
参照官网教程
创建自定义处理器
- 创建 Kotlin 项目
- 在项目中引入 ksp 依赖
1 2 3
| dependencies { implementation("com.google.devtools.ksp:symbol-processing-api:2.1.0-1.0.29") }
|
- 创建自定义注解
1 2 3
| @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class TestAnnotation
|
- 实现 SymbolProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class TestSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> { environment.logger.warn("TestSymbolProcessor begin process!") val symbols = resolver.getSymbolsWithAnnotation(TestAnnotation::class.qualifiedName!!) val list = mutableListOf<KSAnnotated>() symbols.forEach { symbol -> if (!symbol.validate()) list.add(symbol) else symbol.accept(TestSymbolVisitor(environment), Unit) } return list } }
|
- 实现 SymbolProcessorProvider
1 2 3 4 5
| class TestSymbolProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { return TestSymbolProcessor(environment) } }
|
- 实现 KSVisitor
1 2 3 4 5 6 7 8 9
| class TestSymbolVisitor(private val environment: SymbolProcessorEnvironment) : KSVisitorVoid() { override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { if (classDeclaration.annotations.any { it.annotationType.resolve().declaration.qualifiedName?.asString() == TestAnnotation::class.qualifiedName }) { environment.logger.warn(classDeclaration.qualifiedName?.asString() ?: "") } } }
|
symbol.accept
方法的第一个参数需要传入一个 KSVisitor<D, R>
。D 是上下文或数据,R 是返回值。一般情况下如果不需要传递上下文(数据) 以及不需要返回值,使用 KSVisitorVoid
即可。
KSVisitor 提供了 visitX
系列方法,以及一个保底的 visitNode
方法,在里面可以拦截对应扫描到类、注解、方法、参数等,这样就可以进行对应的处理了。
比如利用 KPoet 生成 Kotlin 代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| interface KSVisitor<D, R> { fun visitNode(node: KSNode, data: D): R
fun visitAnnotated(annotated: KSAnnotated, data: D): R
fun visitAnnotation(annotation: KSAnnotation, data: D): R
fun visitModifierListOwner(modifierListOwner: KSModifierListOwner, data: D): R
fun visitDeclaration(declaration: KSDeclaration, data: D): R
fun visitDeclarationContainer(declarationContainer: KSDeclarationContainer, data: D): R
fun visitDynamicReference(reference: KSDynamicReference, data: D): R
fun visitFile(file: KSFile, data: D): R
fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: D): R
fun visitCallableReference(reference: KSCallableReference, data: D): R
fun visitParenthesizedReference(reference: KSParenthesizedReference, data: D): R
fun visitPropertyDeclaration(property: KSPropertyDeclaration, data: D): R
fun visitPropertyAccessor(accessor: KSPropertyAccessor, data: D): R
fun visitPropertyGetter(getter: KSPropertyGetter, data: D): R
fun visitPropertySetter(setter: KSPropertySetter, data: D): R
fun visitReferenceElement(element: KSReferenceElement, data: D): R
fun visitTypeAlias(typeAlias: KSTypeAlias, data: D): R
fun visitTypeArgument(typeArgument: KSTypeArgument, data: D): R
fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: D): R
fun visitTypeParameter(typeParameter: KSTypeParameter, data: D): R
fun visitTypeReference(typeReference: KSTypeReference, data: D): R
fun visitValueParameter(valueParameter: KSValueParameter, data: D): R
fun visitValueArgument(valueArgument: KSValueArgument, data: D): R
fun visitClassifierReference(reference: KSClassifierReference, data: D): R
fun visitDefNonNullReference(reference: KSDefNonNullReference, data: D): R }
|