Kotlin 元编程之 KSP 全面突破

什么是元编程

没想到吧,这世上除了元宇宙,还有元编程,如果没有接触过,可能会有点懵,不过没关系,简单的说就是用代码来生成代码。实现元编程的传统常见手段主要是使用 APT注解处理器 + JavaPoet 组合拳,如果你是作为一名Android 开发者,一定在曾经或者现在使用过很多知名的开源库,比如ButterKnifeARouter等,这些都是基于 注解处理器 + JavaPoet 的方式实现的元编程,是的,虽然元编程这个词很高大上,但是可能你已经默默的使用了很多年了。

元编程就是以源代码作为输入数据的程序,比如编译器、链接器、解释器、调试工具和程序分析工具等等,它们可以在编译时分析源码,对源码进行处理或修改,或者产生中间代码。当然主要的目的还是为了生成代码。

在什么场景下需要元编程呢?
  • 当我们需要生成某种模板代码、样板代码的时候
  • 当我们厌倦了写太多重复代码的时候
  • 当我们需要隐藏实现细节的时候
  • 当我们想要创建语法糖的时候
Kotlin 元编程的常见实现手段
  • Kotlin 反射 / Java 反射
  • Kotlin 注解处理器 (KAPT:Kotlin Annotation Processor Tool)
  • Kotlin 符号处理器 (KSP:Kotlin Symbol Processing)
  • Kotlin 编译器插件(KCP:Kotlin Compiler Plugin)
kotlin 元编程的几种方案对比
Reflection KAPT KSP KCP
运行时 - - -
编译时 - 解析metadata 基于 Kotlin AST 基于 Kotlin AST
复杂度 较低 较高
主要场景 提供动态能力 生成源码 生成源码 生成、修改IR
现状 稳定 稳定 稳定 实验
多平台 JVM + JS JVM 全部 全部
KAPT 的工作机制

在进行Android应用开发时,不少人吐槽 Kotlin 的编译速度慢,而 KAPT 便是拖慢编译的元凶之一。我们知道,Android的很多库都会使用注解简化模板代码,例如 Room、Dagger、Retrofit 等,而默认情况下Kotlin 使用的是 KAPT 来处理注解的。KAPT没有专门的注解处理器,需要借助APT实现的,因此需要先生成 APT 可解析的 stub (Java代码),这拖慢了 Kotlin 的整体编译速度。

在这里插入图片描述
在这里插入图片描述
所以 KAPT 的本质还是基于 Java 注解处理器实现的一个Kotlin 编译器插件。

KAPT 处理 Kotlin 源码存在的问题:

  • 实现复杂,需要手动解析 Kotlin 类信息
  • 编译耗时,KAPT 需将 Kotlin 类转成 Java Stubs
  • 只支持Kotlin-JVM
KCP

KCP是在 kotlinc 过程中提供 Hook 时机,可以在期间解析 AST、修改字节码产物等,Kotlin 的不少语法糖都是 KCP 实现的。例如, data class、 @Parcelize、kotlin-android-extension 等,如今火爆的 Jetpack Compose也是借助 KCP 完成的。

理论上来说, KCP 的能力是 KAPT 的超集,完全可以替代 KAPT 以提升编译速度。但是 KCP 的开发成本太高,涉及 Gradle Plugin、Kotlin Plugin 等的使用,API 涉及一些编译器知识的了解,一般开发者很难掌握。

KSP 简化了KCP的整个流程,开发者无需了解编译器工作原理,处理注解等成本也变得像 KAPT 一样低。

什么是 KSP

KSP 的全称是 Kotlin Symbol Processing ,Kotlin符号处理器,由Google开发,它提供了一套API可以开发轻量级的编译器插件。KSP 官网:https://github.com/google/ksp

KSP本身也是一种KCP插件的实现。

KSP API 根据Kotlin语法在符号级对Kotlin程序结构进行建模。当基于KSP 的插件处理源代码时,可以访问类、类成员、函数和相关参数等结构,但是不能访问 if 块和 for 循环等。

从概念上讲,KSP类似于Kotlin反射中的KType。该API允许处理器从类声明导航到具有特定类型参数的对应类型,反之亦然。还可以替换类型参数、指定方差、应用星型投影和标记类型的可空性。

另一种理解KSP的方式是将其视为Kotlin程序的预处理器框架。编译中的数据流可以按照以下步骤描述:

  1. KSP读取并分析源代码。
  2. KSP生成代码或输出其他形式的产物。
  3. Kotlin编译器将源代码与KSP生成的代码一起编译。

与成熟的编译器插件不同,KSP不能修改代码。因为改变语言语义的编译器插件有时会让人非常困惑。KSP是以只读的方式来处理源代码,从而避免这种情况。

为什么更推荐使用 KSP

KSP 使得创建轻量级编译器插件更加容易

KSP被设计为隐藏编译器更改,最大限度地减少使用它的处理器的维护工作。KSP被设计成不与JVM绑定,因此将来可以更容易地适应其他平台。

KSP VS KCP

KCP相比于KSP的不足:

  • 技能过于复杂,凡人难以驾驭:KCP插件几乎可以访问编译器中的所有内容,具有最大的功能和灵活性,但这种强大的功能是有代价的。即使要编写最简单的插件,你也需要有一些编译器的背景知识,以及对特定编译器的实现细节有一定程度的熟悉。一般的开发者很难在短时间内通过学习成为编译器大师,并且这会花费很多的时间。如果你不需要修改源代码,那么KSP则是一个更好的选择。
  • 依赖项过多,凡人难以维护:由于KCP插件可能依赖于编译器中的任何东西,所以它们对编译器的更改很敏感,需要经常维护。在实际中,插件通常与特定的编译器版本紧密相关,这意味着每次你想要支持一个更新版本的编译器时,你可能需要更新你的插件。

KSP通过定义良好的API隐藏大多数编译器更改,尽管编译器甚至Kotlin语言的重大更改可能仍然需要向API用户公开。KSP试图通过提供一个API来实现常见的用例,该API以功能换取简单性。它的功能是一个通用kotlinc插件的严格子集。例如,kotlinc可以检查表达式和语句,甚至可以修改代码,而KSP不能。

KSP VS 反射

KSP的API看起来类似于kotlin.reflect。它们之间的主要区别是KSP中的类型引用需要显式地解析。这是不共享接口的原因之一。

KSP VS KAPT

KAPT使大量的Java注释处理器可以为Kotlin程序开箱即用。与KAPT相比,KSP的主要优点是改进了构建性能(不依赖于JVM)、更习惯的Kotlin API以及理解Kotlin专用符号的能力。

在性能方面,相比于 KAPT,使用KSP生成代码性能要快2倍以上,因为它省掉了生成 Java Stubs 的耗时过程。

在这里插入图片描述

为了不加修改的直接运行 Java 注解处理器,kapt 将 Kotlin 代码编译为 Java 桩代码(stub),其中保留了 Java 注解处理器关注的信息。为了创建这些桩代码, kapt 需要解析 Kotlin 程序中的所有符号。桩代码生成占据了 kotlinc 完整分析过程的大约 1/3kotlinc 的代码生成过程也是如此。 对于很多注解处理器,这个过程比处理器本身耗费的时间要长很多。比如, Glide 只会分析使用了预定义注解的非常少量的类,它的代码生成非常快速, 几乎所有的构建开销都发生在桩代码生成阶段,切换到 KSP 可以立即减少编译器消耗时间的 25%

kapt 不同, KSP 中的处理器不会以 Java 的方式看待输入程序。 APIKotlin 来说更加自然,尤其是对于 Kotlin 专有的功能,比如顶层函数。由于 KSP 不会象 kapt 那样将处理代理给 javac, 因此它不会依赖于 JVM 专有的行为,并且将来有可能用于其它平台。

KSP 的限制

虽然KSP试图成为大多数常见用例的简单解决方案,但与其他插件解决方案相比,它做了一些权衡。KSP目前存在以下几点限制:

  • 无法做到检查源代码的表达式级信息。
  • 无法修改源代码。
  • 无法 100% 的兼容Java注解处理API。
  • 目前IDEKSP生成的代码无法感知,必须手动为项目配置生成路径。

Kotlin Symbols

大多数处理器通过输入源代码的各种程序结构进行导航。在深入研究API的使用之前,让我们看看从 KSP 的视角来看Kotlin源文件是怎样的:

KSFile
  packageName: KSName
  fileName: String
  annotations: List<KSAnnotation>  (File annotations)
  declarations: List<KSDeclaration>
    KSClassDeclaration // class, interface, object
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      classKind: ClassKind
      primaryConstructor: KSFunctionDeclaration
      superTypes: List<KSTypeReference>
      // contains inner classes, member functions, properties, etc.
      declarations: List<KSDeclaration>
    KSFunctionDeclaration // top level function
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      functionKind: FunctionKind
      extensionReceiver: KSTypeReference?
      returnType: KSTypeReference
      parameters: List<KSValueParameter>
      // contains local classes, local functions, local variables, etc.
      declarations: List<KSDeclaration>
    KSPropertyDeclaration // global variable
      simpleName: KSName
      qualifiedName: KSName
      containingFile: String
      typeParameters: KSTypeParameter
      parentDeclaration: KSDeclaration
      extensionReceiver: KSTypeReference?
      type: KSTypeReference
      getter: KSPropertyGetter
        returnType: KSTypeReference
      setter: KSPropertySetter
        parameter: KSValueParameter

这里列出了一个Kotlin源文件中声明的常见内容如: 类、函数、属性等等。该结构也被称为AST(抽象语法树),类似的, APT/KAPT 则是对 Java AST 的抽象,我们可以找到一些对应关系,比如 Java 使用 Element 描述包、类、方法或者变量等, KSP 中使用 Declaration。

KSP 是如何组织 Kotlin 代码模型的

在这里插入图片描述

类型解析

在 KSP API 的底层实现中, 主要的资源消耗是类型解析。因此类型引用被设计为由处理器明确解析的类型(也有少数例外情况)。当一个类型(Type) (比如 KSFunctionDeclaration.returnTypeKSAnnotation.annotationType)被引用时,它永远是一个 KSTypeReference类型,这是一个带有注解和修饰符的 KSReferenceElement

interface KSFunctionDeclaration : ... {
   
  val returnType: KSTypeReference?
  // ...
}

interface KSTypeReference : KSAnnotated, KSModifierListOwner {
   
  val type: KSReferenceElement
}

一个 KSTypeReference 可以解析为一个 KSType, 它引用到 Kotlin 类型系统中的一个类型。

一个KSTypeReference 拥有一个 KSReferenceElement, 它是 Kotlin 程序结构的数据模型:也就是类型引用是如何编写的。它对应于 Kotlin 语法中的 type 元素。

一个 KSReferenceElement 可以是一个 KSClassifierReferenceKSCallableReference,其中包含很多不需要解析的有用信息。 比如 KSClassifierReference 拥有 referencedName,而 KSCallableReference 拥有 receiverType, functionArguments, 和 returnType

如果需要一个 KSTypeReference 引用的原始声明, 通常可以通过将其解析为 KSType, 并通过访问 KSType.declaration 得到。要从一个类型得到它的类声明, 代码如下:

val ksType: KSType = ksTypeReference.resolve()
val ksDeclaration: KSDeclaration = ksType.declaration

类型解析的代价很高,因此需要明确调用。通过解析得到的有些信息在 KSReferenceElement 中已经存在了。 比如, 通过 KSClassifierReference.referencedName 可以过滤掉很多不感兴趣的元素。你应该只有在需要从 KSDeclarationKSType 得到具体信息的时候才进行类型解析。

指向一个函数类型的 KSTypeReference 在它的元素中已经有了大部分信息。尽管可以解析到 Function0, Function1 等等的函数群, 但这些解析不会带来比 KSCallableReference 更多的任何信息。有一种情况需要解析函数类型引用,就是处理函数原型(Function Prototype)的 identity.

KSP 和 Java 中的程序元素对应关系

Java / APT KSP 中的类似功能 注意事项
AnnotationMirror KSAnnotation
AnnotationValue KSValueArguments
Element KSDeclaration/KSDeclarationContainer
ExecutableElement KSFunctionDeclaration
PackageElement KSFile KSP不会将package建模为程序元素
ExecuteableElement KSFunctionDeclaration 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素
TypeElement KSClassDeclaration 一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口
VariableElement KSVariableParameter / KSPropertyDeclaration 一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
Parameterizable KSDeclaration
QualifiedNameable KSDeclaration
TypeElement KSClassDeclaration
TypeParameterElement KSTypeParameter
VariableElement KSValueParameter/KSPropertyDeclaration
类型

KSP 要求明确解析类型, 因此在解析之前, Java 中的有些功能只能通过 KSType 和对应的元素得到.

Java / APT KSP 中的类似功能 注意事项
ArrayType KSBuiltIns.arrayType
DeclaredType KSType / KSClassifierReference
ErrorType KSType.isError
ExecutableType KSType / KSCallableReference
IntersectionType KSType / KSTypeParameter
NoType KSType.isError KSP 中没有这样的功能
NullType KSP 中没有这样的功能
PrimitiveType KSBuiltIns 与 Java 中的基本类型不完全相同
ReferenceType KSTypeReference
TypeMirror KSType
TypeVariable KSTypeParameter
UnionType 没有这样的功能 Kotlin 的 每个 catch 代码段只有 1 个类型.
即使对 Java 注解处理器来说, UnionType 也是不可访问的
WildcardType KSType / KSTypeArgument
杂项
Java / APT KSP 中的类似功能 注意事项
Name KSName
ElementKind ClassKind / FunctionKind
Modifier Modifier
NestingKind ClassKind / FunctionKind
AnnotationValueVisitor
ElementVisitor KSVisitor
AnnotatedConstruct KSAnnotated
TypeVisitor
TypeKind KSBuiltIns 有些可以在 builtin 中得到, 其他通过 KSClassDeclaration 得到 DeclaredType
ElementFilter Collection.filterIsInstance
ElementKindVisitor KSVisitor
ElementScanner KSTopDownVisitor
SimpleAnnotationValueVisitor KSP 中不需要
SimpleElementVisitor KSVisitor
SimpleTypeVisitor
TypeKindVisitor
Types Resolver / utils 有些 utils 也被集成在符号接口中
Elements Resolver / utils
细节

这部分介绍 KSP 怎样提供 Java 注解处理 API 的功能.

AnnotationMirror
Java KSP 中的同等功能
getAnnotationType ksAnnotation.annotationType
getElementValues ksAnnotation.arguments
AnnotationValue
Java KSP 中的同等功能
getValue ksValueArgument.value
Element
Java KSP 中的同等功能
asType ksClassDeclaration.asType(…)
getAnnotation 未实现
getAnnotationMirrors ksDeclaration.annotations
getEnclosedElements ksDeclarationContainer.declarations
getEnclosingElements ksDeclaration.parentDeclaration
getKind 通过 ClassKind 或 FunctionKind 进行类型检查和转换
getModifiers ksDeclaration.modifiers
getSimpleName ksDeclaration.simpleName
ExecutableElement
Java KSP 中的同等功能
getDefaultValue 未实现
getParameters ksFunctionDeclaration.parameters
getReceiverType ksFunctionDeclaration.parentDeclaration
getReturnType ksFunctionDeclaration.returnType
getSimpleName ksFunctionDeclaration.simpleName
getThrownTypes Kotlin 中不需要
getTypeParameters ksFunctionDeclaration.typeParameters
isDefault 检查父类型是不是接口
isVarArgs ksFunctionDeclaration.parameters.any { it.isVarArg }
Parameterizable
Java KSP 中的同等功能
getTypeParameters ksFunctionDeclaration.typeParameters
QualifiedNameable
Java KSP 中的同等功能
getQualifiedName ksDeclaration.qualifiedName
TypeElement
Java KSP 中的同等功能
getEnclosedElements ksClassDeclaration.declarations
getEnclosingElement ksClassDeclaration.parentDeclaration
getInterfaces // 不需要类型解析也应该能够实现
ksClassDeclaration.superTypes
.map { it.resolve() }
.filter { (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE }
getNestingKind Check KSClassDeclaration.parentDeclaration 和 inner 修饰符
getQualifiedName ksClassDeclaration.qualifiedName
getSimpleName ksClassDeclaration.simpleName
getSuperclass // 不需要类型解析也应该能够实现
ksClassDeclaration.superTypes
.map { it.resolve() }
.filter { (it?.declaration as? KSClassDeclaration)?.classKind == ClassKind.CLASS }
getTypeParameters ksClassDeclaration.typeParameters
TypeParameterElement
Java KSP 中的同等功能
getBounds ksTypeParameter.bounds
getEnclosingElement ksTypeParameter.parentDeclaration
getGenericElement ksTypeParameter.parentDeclaration
VariableElement
Java KSP 中的同等功能
getConstantValue 未实现
getEnclosingElement ksValueParameter.parentDeclaration
getSimpleName ksValueParameter.simpleName
ArrayType
Java KSP 中的同等功能
getComponentType ksType.arguments.first()
DeclaredType
Java KSP 中的同等功能
asElement ksType.declaration
getEnclosingType ksType.declaration.parentDeclaration
getTypeArguments ksType.arguments
ExecutableType

函数的 KSType 只是一个签名, 由 FunctionN<R, T1, T2, ..., TN> 群表达.

Java KSP 中的同等功能
getParameterTypes ksType.declaration.typeParameters, ksFunctionDeclaration.parameters.map { it.type }
getReceiverType ksFunctionDeclaration.parentDeclaration.asType(…)
getReturnType ksType.declaration.typeParameters.last()
getThrownTypes Kotlin 中不需要
getTypeVariables ksFunctionDeclaration.typeParameters
Elements
Java KSP 中的同等功能
getAllAnnotationMirrors KSDeclarations.annotations
getAllMembers getAllFunctions, getAllProperties未实现
getBinaryName 未决定, 参见 Java Specification
getConstantExpression 常数值, 而不是表达式
getDocComment 未实现
getElementValuesWithDefaults 未实现
getName resolver.getKSNameFromString
getPackageElement 不支持包, 但可以取得包信息. KSP 中不能对包进行操作.
getPackageOf 不支持包
getTypeElement Resolver.getClassDeclarationByName
hides 未实现
isDeprecated KsDeclaration.annotations.any { it.annotationType.resolve()!!.declaration.qualifiedName!!.asString()== Deprecated::class.qualifiedName}
overrides KSFunctionDeclaration.overrides / KSPropertyDeclaration.overrides (各个类的成员函数)
printElements KSP 对大多数类有基本的 toString() 实现
Types
Java KSP 中的同等功能
asElement ksType.declaration
asMemberOf resolver.asMemberOf
boxedClass 不需要
capture 未决定
contains KSType.isAssignableFrom
directSuperTypes (ksType.declaration as KSClassDeclaration).superTypes
erasure ksType.starProjection()
getArrayType ksBuiltIns.arrayType.replace(…)
getDeclaredType ksClassDeclaration.asType
getNoType ksBuiltIns.nothingType / null
getNullType 根据上下文确定, 可能可以使用 KSType.markNullable
getPrimitiveType 不需要, 检查 KSBuiltins
getWildcardType 在需要 KSTypeArgument 的地方使用 Variance
isAssignable ksType.isAssignableFrom
isSameType ksType.equals
isSubsignature functionTypeA == functionTypeB / functionTypeA == functionTypeB.starProjection()
isSubtype ksType.isAssignableFrom
unboxedType 不需要

KSP 的使用

依赖配置

在 Android Studio 已有的kotlin项目中新建一个普通的 library 工程作为KSP处理模块(其他IDE配置请参考官网),在其build.gradle中添加如下配置:

plugins {
   
    id 'java-library'
    id 'org.jetbrains.kotlin.jvm'
}

java {
   
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

java.sourceSets {
   
    main {
   
        java.srcDirs += "src/main/kotlin"
    }
}

dependencies {
    
    implementation 'com.google.devtools.ksp:symbol-processing-api:1.7.21-1.0.8'
}

ksp依赖库的版本需要根据项目使用的kotlin版本来决定,最新版本的Android Studio一般默认是在根目录下的build.gradle中可以找到kotlin版本配置。然后到KSP的发布页找到对应的KSP版本即可:https://github.com/google/ksp/releases

SymbolProcessorProvider : KSP 的入口

在 library module 中需要新建一个 SymbolProcessorProvider 的实现类作为KSP的入口,SymbolProcessorProvider 接口的代码如下:

interface SymbolProcessorProvider {
   
    fun create(environment: SymbolProcessorEnvironment): SymbolProcessor
}

可以看到它只有一个 create 方法,该方法需要返回一个实现 SymbolProcessor 接口的对象,而 create 方法的入参SymbolProcessorEnvironment 主要就是用来给创建 SymbolProcessor 对象用的,通过environment参数可以获取到 KSP 运行时的相关依赖,我们只需将这些依赖注入到自定义的 Processor 对象即可。

class ProcessorProvider : SymbolProcessorProvider {
   
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
   
        return Processor(environment.codeGenerator, environment.logger,environment.options)
    }
}

这里创建Processor使用了SymbolProcessorEnvironment 的三个字段:

  • codeGenerator:可以用来生成代码文件
  • logger:可以用来输出日志
  • options:可以用来接受命令行或Gradle插件中的配置参数

一般来说,codeGenerator 参数是一定要的,因为你总要生成代码吧,而其他的参数可以根据自己的需要选择。通过查看SymbolProcessorEnvironment的源码可以知道全部可用的字段:

class SymbolProcessorEnvironment(
    /**
     * passed from command line, Gradle, etc.
     */
    val options: Map<String, String>,

    /**
     * language version of compilation environment.
     */
    val kotlinVersion: KotlinVersion,

    /**
     * creates managed files.
     */
    val codeGenerator: CodeGenerator,

    /**
     * for logging to build output.
     */
    val logger: KSPLogger,

    /**
     * Kotlin API version of compilation environment.
     */
    val apiVersion: KotlinVersion,

    /**
     * Kotlin compiler version of compilation environment.
     */
    val compilerVersion: KotlinVersion,

    /**
     * Information of target platforms
     *
     * There can be multiple platforms in a metadata compilation.
     */
    val platforms: List<PlatformInfo>,
) {
   ...}

当我们创建好 SymbolProcessorProvider 对象后就可以先将其添加到src/main/resources/META-INF/services/路径下的一个名为com.google.devtools.ksp.processing.SymbolProcessorProvider的文件中:

在这里插入图片描述

在上面的文件中,输入ProcessorProvider对象的全类名,例如:

com.fly.ksp.processor.ProcessorProvider
SymbolProcessor

SymbolProcessor 接口类就是KSP开发时唯一需要重点关注的类

interface SymbolProcessor {
   
    fun process(resolver: Resolver): List<KSAnnotated> // 重点关注
    fun finish() {
   }
    fun onError() {
   }
}

它有三个方法,但唯一需要覆写的只有 process 这个方法,下面定义一个类来实现该接口:

class Processor(val codeGenerator: CodeGenerator, val logger: KSPLogger) : SymbolProcessor {
   

    val functions = mutableListOf<String>()
    val visitor = FindFunctionsVisitor()

    override fun process(resolver: Resolver) {
   
        resolver.getAllFiles().map {
    it.accept(visitor, Unit) }
    }
}
KSVisitor

SymbolProcessor.process() 方法提供了一个 Resolver , 来解析源文件的 symbols,而Resolver 使用访问者模式去遍历 AST,需要一个KSVisitor参数。

下面代码定义了一个 FindFunctionsVisitorResolver 使用,在这个Visitor中负责找出当前 KSFile 中的 top-levelfunction 以及 Class 成员方法。

class FindFunctionsVisitor : KSVisitorVoid() {
   
     override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
   
         classDeclaration.getDeclaredFunctions().map {
    it.accept(this, Unit) }
     }

     override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
   
         functions.add(function)
     }

     override fun visitFile(file: KSFile, data: Unit) {
   
         file.declarations.map {
    it.accept(this, Unit) }
     }
}
一些 KSP API 示例
得到所有成员函数
fun KSClassDeclaration.getDeclaredFunctions(): List<KSFunctionDeclaration> =
    declarations.filterIsInstance<KSFunctionDeclaration>()
检查一个类或函数是否为 local
fun KSDeclaration.isLocal(): Boolean =
    parentDeclaration != null && parentDeclaration !is KSClassDeclaration
查找类型别名指向的实际的类或接口声明
fun KSTypeAlias.findActualType(): KSClassDeclaration {
   
    val resolvedType = this.type.resolve().declaration
    return if (resolvedType is KSTypeAlias) {
   
        resolvedType.findActualType()
    } else {
   
        resolvedType as KSClassDeclaration
    }
}
在源代码文件的注解中查找被压制(Suppressed)的名称
// @file:kotlin.Suppress("Example1", "Example2")
fun KSFile.suppressedNames(): List<String> {
   
    val ignoredNames = mutableListOf<String>()
    annotations.filter {
   
        it.shortName.asString() == "Suppress" && it.annotationType.resolve()?.declaration?.qualifiedName?.asString() == "kotlin.Suppress"
    }.forEach {
   
        val argValues: List<String> = it.arguments.flatMap {
    it.value }
        ignoredNames.addAll(argValues)
    }
    return ignoredNames
}

一个简单的 demo

现在有一个类,代码如下,假如我们现在想要为其生成建造者模式的代码

class AClass(private val a: Int, val b: String, val c: Double) {
   
    val p = "$a, $b, $c"
    fun foo() = p
}

尽管 kotlin 中支持默认参数值和命名参数,基本上可以取代建造者模式的使用了,但是假如你更喜欢建造者模式的使用方式,你仍然可以通过代码来编写它,问题是这样的代码有大量重复的样板代码需要编写,十分的消耗体力,那么此时使用KSP就可以为我们节省劳动力。

假如我们期望在生成建造者模式的代码之后使用方式如下:

@Builder
class AClass(private val a: Int, val b: String, val c: Double) {
   
    val p = "$a, $b, $c"
    fun foo() = p
}

fun main() {
    
    val a = AClassBuilder()
        
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值