使用KSP处理注解和生成Kotlin代码

 前言

ksp是Kotlin 符号处理工具,类似于kapt(apt),可用于开发轻量级编译器插件,github地址:google/ksp: Kotlin Symbol Processing API (github.com)

也可以理解为注解处理和编译时代码生成工具(也能处理所有文件,不止被注解标记的)

既然有了kapt(apt),为什么还要有ksp?

  1. 速度比kapt更快,性能更好
  2. 对kotlin的支持更好
  3. 适用于Kotlin Multiplatform
  4. 可以直接生成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栏里看到的:

结语

可以看到,其实使用起来也不是特别麻烦,而有了这个功能后,又可以做很多骚操作了

参考:

KSP 快速入门|科特林 (kotlinlang.org)

google/ksp: Kotlin Symbol Processing API (github.com)

api参考:

Java annotation processing to KSP reference | Kotlin (kotlinlang.org)

示例:

ltttttttttttt/Buff: Add status to beans in Compose, Fields in beans can be directly used as the State<T> (github.com)

ksp/examples/playground at main · google/ksp (github.com)

猜你喜欢

转载自blog.csdn.net/qq_33505109/article/details/127448020
今日推荐