一文搞懂Kotlin符号处理接口KSP

公众号「稀有猿诉」

Kotlin符号处理(Kotlin Symbol Processing)即KSP是可以用于开发轻量级编译器插件的一套API。是Kotlin原生的,Kotlin语法友好的编译器插件。使用简单且易于上手,可以实现一些非常强大的编译时代码处理功能,如代码生成和代码检查。今天就来学习一下KSP的基本原理,以及如何使用KSP API。

注意 ,本文是Kotlin中较为高级的话题,适合有一定的Kotlin基础的同学,否则理解起来可能有难度,可以事先阅读前面的文章

什么是KSP

与前文提到的注解处理器kapt类似,KSP也是一种编译时的插件,能够在编译前处理Kotlin语言的符号。KSP API能地道地处理Kotlin的源码,因为它是专门为Kotlin而设计的,能够完全的理解和识别Kotlin的语言符号,以及Kotlin专属的特性:如扩展函数,声明点泛型变化以及本地函数。KSP API基于Kotlin的语法,把Kotlin程序拆解为各种静态的符号,可以处理如类,成员,函数,参数 以及注解等等。但它并不是运行时的(那是反射做的事情),因此像逻辑如循环和条件语句是没有办法进行处理,以及也无法得到表达式的结果。

虽然KSP是编译器插件,但它是运行在最终编译之前,也就是说在编译器编译全部代码之前,事先会运行KSP插件。所以KSP API最适合做的事情是:

  1. 读取代码和各种资源文件,并进行分析
  2. 生成代码

接下来看如何具体使用KSP API。

配置KSP

KSP是由谷歌开发的一套工具,包括两部分一个是Kotlin plugin,另一个是依赖库。所以需要在项目的根build.gradle里面,先把plugin添加到项目里:

Groovy 复制代码
// The root build.gradle of your project
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.9.23' apply false
    id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false
}

当然,这一步其实并不是必须的,也可以在每个模块中再配置plugin。

接下来,在使用KSP的模块里面添加plugin,添加依赖以及指明KSP processor,这是最为关键的配置:

Groovy 复制代码
// module build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm'
    id 'com.google.devtools.ksp'
}

dependencies {
    implementation project(':kspannotation')
    ksp project(':kspprocessor')
}

如果项目顶层指定了plugin的版本,那么到了module这里,就不必再指定版本了。另外就是要注意版本的匹配,ksp的版本前半段『1.9.23』指明 的是最低的Kotlin版本要求。最好是让ksp要求的版本与指定的Kotlin版本匹配或者差距较小,否则可能会有问题。dependencies中的ksp指定的是KSP processor,对于有些库可能注解和定义和KSP的processor可能会在同一个包里,那么写一句就够了,如Room的,就一句:ksp 'androidx.room:room-compiler:2.6.1'。

如果是自定义的processor,需要为processor单独建一个library module,配置ksp库为依赖即可:

Groovy 复制代码
// KSP processor module build.gradle
plugins {
    id 'org.jetbrains.kotlin.jvm'
}

dependencies {
    implementation project(':kspannotation')

    implementation 'com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20'
    implementation 'com.squareup:kotlinpoet-ksp:1.16.0'
}

典型的KSP procesor(包括网上大部分的例子)都是分了三个module,一个是定义注解的module,一个是实现processor的,一个是使用注解和processor的。但这并不是必须的,为了方便,其实把注解的定义和processor放在一个module就可以了。只要把processor与使用它的module分开来了,就可以。

注意:对于processor module来说它的类型要是library,并且要是Java library或者Kotlin library,因为这是Kotlin语言层面的东西。对于Android同学来说在新建module时一定要选择『Java or Kotlin Library』。

实现KSP Processor

配置好了模块后,剩下的就是要实现一个KSP processor了。

实现Processor

大部分工作plugin已经做好了,我们需要做的就是实现一些接口。有两个需要实现,一个是SymbolProcessorProvider,另一个则是SymbolProcessor

SymbolProcessorProvider相当于是processor的一个工厂方法,我们实现它的create方法,返回一个SymbolProcessor实例,一个典型的实现:

Kotlin 复制代码
class MyProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return MyProcessor(environment.codeGenerator)
    }
}

它就相当于一个工厂方法,把上下文环境传给processor,SymbolProcessor是重点,我们需要实现它的process方法,针对感兴趣的符号进行处理,比如用KotlinPoet生成代码,这里是发挥创造力的地方:

Kotlin 复制代码
class MyProcessor(private val generator: CodeGenerator) : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSAnnotated> {
        val annotatedClasses = resolver
            .getSymbolsWithAnnotation(MyAnnotation::class.java.name)
            .filterIsInstance<KSClassDeclaration>()

        for (aclass in annotatedClasses) {
            val packageName = aclass.packageName.asString()
            val className = aclass.simpleName.asString()
            val methods = aclass.getDeclaredFunctions())
            // ...
         }
        return emptyList()
    }
}

注册Processor

实现了process后还需要把process注册一下,否则ksp plugin无法找到这个processor。在processor module与代码同级文件夹下新建文件『resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider』,然后把刚才实现的provider的完整类名,写在文件里,如果是使用IDE一般都会有提示的。

Kotlin 复制代码
// myprocessor/src/main/
//    |-- kotlin/net/toughcoder/
//              |-- MyProcessorProvider.kt
//              |-- MyProcessor.kt
//    |-- resources/META-INF/services/
//              |-- com.google.devtools.ksp.processing.SymbolProcessorProvider
// file: resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
net.toughcoder.MyProcessorProvider

为啥要用KSP

目前来说KSP最主要应用仍然 是注解的处理,以及配合注解进行代码生成。通过前面一篇关于注解的文章中我们知道,注解的处理已经有了一个专门的工具了叫做kapt,就目前来说KSP能做的事情kapt也都能做,它们都是用于编译时代码处理以及代码生成,都能处理注解。那么,在已经有了kapt的前提下,为啥还要搞KSP呢?

kapt虽然是Kotlin的注解处理器,但是它保持Java的兼容性,它直接复用了Java的AbstractProcessor,要依赖于Java的annotation procssor以及javac,只适用于Kotlin/JVM,其他target用不起来,因此它并不能算是Kotlin原生的工具,对Kotlin的特性支持不友好。再有就是,为了保持与javac的兼容性,它的处理速度很慢,必须先把Kotlin代码转成javac能认识的标准Java代码,这肯定会有不必要的性能开销。基于这些限制,kapt已经停止开发了,处于维护状态了,不会再添加新功能了。省流点来说,kapt是以Java角度来看待输入代码的(即也要处理的源码),而KSP是以Kotlin角度

KSP则是Kotlin原生的,基于Kotlin开发的,且是为了Kotlin开发的,并不受限于javac,因此所有的Kotlin目标平台都能用。并且对Kotlin的特性支持的很友好。它的处理速度也较kapt有提升,因为不必要做编码转换了,省了一道工序。从官方给出的数据看至少能省25%的编译时间。另外,KSP的API使用起来更加的Kotlin友好一些SymbolProcessor传递过来的Resolver有很方便的接口可以取得被标的类,而且符号对象是KSClassDeclaration,它可以方便的取一个Kotlin类的相关的其他符号,如包名,类名,方法等。

总结

通过本文我们理解了KSP的概念,并学会了如何在项目中配置KSP, 以及如何实现一个KSP processor。KSP视Kotlin代码为一系列的静态符号,对Kotlin语言特性支持友好,处于活跃的开发状态且被官方大力支持,因此应该尽早转向KSP。并且相信KSP能做的事情会越来越多。

参考资料

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

原创不易,「打赏」「点赞」「在看」「收藏」「分享」 总要有一个吧!

相关推荐
程序员老刘·17 分钟前
如何评价Flutter?
android·flutter·ios
JoyceMill2 小时前
Android 图像效果的奥秘
android
想要打 Acm 的小周同学呀3 小时前
ThreadLocal学习
android·java·学习
天下是个小趴菜3 小时前
蚁剑编码器编写——中篇
android
命运之手3 小时前
【Android】自定义换肤框架05之Skinner框架集成
android·skinner·换肤框架·不重启换肤·无侵入换肤
DS小龙哥3 小时前
QT+OpenCV在Android上实现人脸实时检测与目标检测
android·人工智能·qt·opencv·目标检测
SwBack4 小时前
【pearcmd】通过pearcmd.php 进行GetShell
android·开发语言·php
miao_zz4 小时前
react native中依赖@react-native-clipboard/clipboard库实现文本复制以及在app中获取复制的文本内容
android·react native·react.js
小羊子说4 小时前
Android 开发中 C++ 和Java 日志调试
android·java·c++
Android 开发者4 小时前
平台稳定性里程碑 | Android 15 Beta 3 已发布
android