KSP是什么
KSP,全称Kotlin Symbol Processing,我的翻译是kotlin符号处理程序,是KAPT(Kotlin Annotation Processor Tool)的下一代替代品。功能和KAPT差不多,也是方便处理注解、生成代码的,但是性能会高很多。
开始尝试KSP
不管是官网还是内网,关于KSP的使用方法都很模糊。官网提供了各种API文档,也提供了最基本的一个示例,但是在示例里面只是单纯记录了信息,没有保存下来,没有什么用。内网也只是单纯翻译官方文档,没有价值。于是只能阅读别人的代码,摸索写下一个简单的组件。
初始化项目
创建一下项目,新开3个模块,分别是ann、web、ksp。
在根目录下的build.gradle.kts
里设置一下仓库地址:
subprojects {
repositories {
maven("https://maven.aliyun.com/repository/central")
maven("https://maven.aliyun.com/repository/spring")
}
}
buildscript {
repositories {
maven("https://maven.aliyun.com/repository/central")
maven("https://maven.aliyun.com/repository/spring")
}
dependencies {
classpath(kotlin("gradle-plugin", version = "1.7.10"))
}
}
先写ann,这是放注解的,在build.gradle.kts
里指定使用kotlin即可:
plugins {
kotlin("jvm")
}
然后创建一下注解Woo
:
package com.small.ann
annotation class Woo(val right:String)
ann模块就完成了。
ksp模块的build.gradle.kts
如下:
plugins {
kotlin("jvm")
}
dependencies {
implementation("com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework:spring-web:5.3.21")
implementation(project(":ann"))
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
注意:我使用的kotlin版本为1.7.10,ksp版本为1.0.6,所以是com.google.devtools.ksp:symbol-processing-api:1.7.10-1.0.6
。使用前需要确定ksp和kotlin的对应关系。
web模块的build.gradle.kts
如下:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.1"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm")
kotlin("plugin.spring") version "1.7.10"
id("com.google.devtools.ksp") version "1.7.10-1.0.6"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
ksp(project(":kapt"))
implementation(project(":ann"))
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
kotlin {//将生成出来的文件夹加到代码源中,让IDE识别
sourceSets.main {
kotlin.srcDir("build/generated/ksp/main/kotlin")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
web模块是从模板创建的,关键点是使用id("com.google.devtools.ksp") version "1.7.10-1.0.6"
插件,并指定ksp依赖ksp(project(":kapt"))
,然后将生成出来的文件夹加到代码源中,让IDE知道。
完成web模块
web模块除了SpringBootApplication
外就一个控制器文件,如下:
package com.example.demo.server
import com.small.kapt.Xixi
import com.small.ann.Woo
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class Blank {
@GetMapping("/good")
@Woo("nice")
fun hello():String{
println(Xixi.nice)
return "hi"
}
}
其中com.small.kapt.Xixi
类是生成出来的代码,后面再说。
完成ksp模块
现在开始进入正题。KSP主体有两个类,SymbolProcessorProvider
和SymbolProcessor
。前面是注册器,后面是具体执行的。注册器很简单,创建一个自己的SymbolProcessor
即可:
package com.small.kapt
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class SmallProvider:SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return SmallProcessor(environment)
}
}
然后需要在resources文件夹创建META-INF/services文件夹,并在services文件夹创建文件com.google.devtools.ksp.processing.SymbolProcessorProvider
。内容为com.small.kapt.SmallProvider
,即创建的SymbolProcessor
完整路径。
具体工作是在SymbolProcessor
中执行的,代码如下:
package com.small.kapt
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.containingFile
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.isAnnotationPresent
import com.google.devtools.ksp.processing.Dependencies
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.symbol.*
import com.small.ann.Woo
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
class SmallProcessor(private val environment: SymbolProcessorEnvironment): SymbolProcessor {
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
val path2right=HashMap<String,String>()
val dep=ArrayList<KSFile>()
resolver.getSymbolsWithAnnotation(Woo::class.java.name).forEach {
dep.add(it.containingFile!!)
val right=it.getAnnotationsByType(Woo::class).first().right
val path=it.getAnnotationsByType(GetMapping::class).first().value[0]
path2right[path] = right
}
if (path2right.isNotEmpty()){
val sb=StringBuilder("package com.small.kapt\nobject Xixi{\n")
path2right.forEach {(path,right) ->
sb.appendLine("const val $right=\"$path\"")
}
sb.append("}")
environment.codeGenerator.createNewFile(Dependencies(false,*dep.toTypedArray()),"com.small.kapt","Xixi")
.write(sb.toString().encodeToByteArray())
}
return emptyList()
}
}
采集到的数据都存在path2right中,同时把对应依赖文件存在dep中。遍历完成后,使用codeGenerator
创建文件,传入依赖文件列表,这样方便缓存。当依赖文件列表中的文件没有改动的时候,就会跳过KSP过程,节约时间,出现的信息会是:web:kspKotlin UP-TO-DATE
。只有文件有改动才会触发重新生成文件。可以把依赖列表改成Dependencies.ALL_FILES
来强制每次生成。
这里是直接通过拼接字符串的方式实现的,对于稍微复杂一点的内容,建议使用kotlinPoet,创建文件更方便。build web项目后,生成的代码如下:
package com.small.kapt
object Xixi{
const val nice="/good"
}
位于build/generated/ksp/main/kotlin
,这就是web模块中使用的Xixi类了。
网友评论