前言
ksp是Kotlin 符号处理工具,类似于kapt(apt),可用于开发轻量级编译器插件,github地址:google/ksp: Kotlin Symbol Processing API (github.com)
也可以理解为注解处理和编译时代码生成工具(也能处理所有文件,不止被注解标记的)
既然有了kapt(apt),为什么还要有ksp?
- 速度比kapt更快,性能更好
- 对kotlin的支持更好
- 适用于Kotlin Multiplatform
- 可以直接生成kotlin代码文件(实际上可以生成任意类型的文件)
ps:ksp和kapt(apt)一样,都只能生成新代码文件,无法修改源代码
正文
如何创建一个自己的ksp处理器呢,我们开始一步一步的接入并处理注解然后生成一个.kt文件
1.创建ksp model
首先需要你先有一个支持kotlin的现有项目或空项目
然后new一个Kotlin Library
ksp model的build.gradle.kts增加ksp的依赖:
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation("com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6")//引入ksp
}
ps:ksp最新版本查看(需要对应当前使用的kotlin版本):Releases · google/ksp (github.com)
2.新建ksp处理器并注册
创建TestKsp.kt,修改如下代码:
package com.lt.ksp
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.google.devtools.ksp.validate
/**
* creator: lt 2022/10/20 [email protected]
* effect : ksp处理程序的创建
* warning:
*/
internal class TestKspSymbolProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
TestKspSymbolProcessor(environment)
}
/**
* creator: lt 2022/10/20 [email protected]
* effect : ksp处理程序
* warning:
*/
internal class TestKspSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(TestKsp::class.qualifiedName!!)
val ret = mutableListOf<KSAnnotated>()
symbols.toList().forEach {
if (!it.validate())
ret.add(it)
else
it.accept(TestKspVisitor(environment), Unit)//处理符号
}
//返回无法处理的符号
return ret
}
}
/**
* creator: lt 2022/10/20 [email protected]
* effect : 访问并处理相应符号
* warning:
*/
internal class TestKspVisitor(private val environment: SymbolProcessorEnvironment) : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
//todo 在这里处理
}
}
/**
* creator: lt 2022/10/20 [email protected]
* effect : 测试注解
* warning:
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class TestKsp
创建特定路径的文件,用来指定ksp处理器的创建器的全类名(注册ksp)
ps:路径: ksp\src\main\resources\META-INF\services\com.google.devtools.ksp.processing.SymbolProcessorProvider
文件内写上创建器的全类名
3.在app model中引入你的ksp model
在你的app model下的build.gradle.kts中修改:
plugins {
...
id("com.google.devtools.ksp") version "1.7.10-1.0.6"//引入ksp插件
}
//如果是安卓项目
android {
...
buildTypes {
release {
...
kotlin {
sourceSets.main {
//增加代码目录(将ksp生成的代码加入代码目录中)
kotlin.srcDir("build/generated/ksp/release/kotlin")
}
}
}
debug {
...
kotlin {
sourceSets.main {
kotlin.srcDir("build/generated/ksp/debug/kotlin")
}
}
}
}
kotlin {
sourceSets.test {
kotlin.srcDir("build/generated/ksp/test/kotlin")
}
}
}
//如果是kotlin项目
sourceSets {
main {
java.srcDirs("build/generated/ksp/main/kotlin")
}
}
dependencies {
...
implementation(project(":ksp"))//引入刚才新建的ksp model
ksp(project(":ksp"))
}
4.处理注解并生成代码
我们给MainActivity加上TestKsp注解,然后创建一个MyMainActivity.kt,然后创建一个字段,中间保存MainActivity所有的字段名和类型
修改TestKspVisitor如下:
/**
* creator: lt 2022/10/20 [email protected]
* effect : 访问并处理相应符号
* warning:
*/
internal class TestKspVisitor(private val environment: SymbolProcessorEnvironment) :
KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
val packageName = classDeclaration.containingFile!!.packageName.asString()//获取这个类的包名
val originalClassName = classDeclaration.simpleName.asString()//获取类名
val className = "My${originalClassName}"
val file = environment.codeGenerator.createNewFile(//创建新的文件(默认.kt)
Dependencies(
true,
classDeclaration.containingFile!!
), packageName, className
)
file.write("package $packageName\n\n".toByteArray())//写入文件
file.write("class $className {\n".toByteArray())
file.write(" val fields = ".toByteArray())
val fields = classDeclaration.getAllProperties().map {//遍历所有的属性
val name = it.simpleName.getShortName()//属性名
val type = it.type.resolve().toString()//属性类型
"$name: $type"
}.joinToString()
file.write("\"$fields\"\n".toByteArray())
file.write("}".toByteArray())
file.close()
}
}
然后我们运行项目,就可以自动生成代码了,我们去到build文件夹中找到生成的代码
我们就可以直接使用生成的代码了
补充
日志打印无法使用println,且无法使用调试(可能是我没找到方法),而是使用:
environment.logger.warn("log")
而且是需要使用warn,不然找不到日志,其次日志是在build栏里看到的:
结语
可以看到,其实使用起来也不是特别麻烦,而有了这个功能后,又可以做很多骚操作了
参考:
google/ksp: Kotlin Symbol Processing API (github.com)
api参考:
Java annotation processing to KSP reference | Kotlin (kotlinlang.org)
示例: