Android 技术文章

kotlin KSP小试牛刀

2022-07-22  本文已影响0人  魂狩

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主体有两个类,SymbolProcessorProviderSymbolProcessor。前面是注册器,后面是具体执行的。注册器很简单,创建一个自己的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类了。

上一篇下一篇

猜你喜欢

热点阅读