6.1.5 在 Kotlin 项目中使用注解处理器

原文链接: https://kotlinlang.org/docs/jvm-annotation-processors.html

6.1.5 在 Kotlin 项目中使用注解处理器

  • 在以下情况使用 kapt:* 你有一个 Maven 项目。* 你有一个 Gradle 项目,但所需的 Java 注解处理器尚不支持 KSP。查看受支持库的列表。* 在以下情况使用 KSP:* 你有一个 Gradle 项目,并且所需的 Java 注解处理器支持 KSP。* 你想创建自己的注解处理器。

注解处理器在编译期分析你的源代码,以生成样板代码、校验用法或产出其他构件。Kotlin 支持两种使用注解处理器的方式:

  • kapt 编译器插件的工作方式是从 Kotlin 源代码生成桩文件,然后对这些桩文件运行 Java 注解处理器。这个额外的生成桩步骤会让构建更慢,也意味着 kapt 无法理解 Kotlin 特有的结构,例如扩展函数或空安全。

kapt 同时支持 Maven 和 Gradle。建议所有 Maven 项目以及使用尚未采用 KSP 的处理器库(例如 MapStruct)的 Gradle 项目都使用它。

  • KSP 框架通过 Kotlin 优先的 API 直接读取 Kotlin 源代码,而无需生成桩文件。它原生理解 Kotlin 特有特性,构建速度比 kapt 更快。

目前,KSP 只对 Gradle 提供官方支持。建议在编写自己的处理器以及使用 KSP 兼容库(例如 Dagger)时使用它。

用 kapt 配合 Java 注解处理器

kapt 让你可以在 Kotlin 项目中使用现有的 Java 注解处理器,而无需对处理器本身做任何修改。

下面的例子演示如何使用 MapStruct 注解处理器,它在编译期生成 Java bean 之间的类型安全映射实现。

  1. 在构建文件中应用 kapt 插件,并把 MapStruct 添加到 dependencies 部分:

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
   <properties>
       <kotlin.compiler.jvmTarget>11</kotlin.compiler.jvmTarget>
       <mapstruct.version>1.6.3</mapstruct.version>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.mapstruct</groupId>
           <artifactId>mapstruct</artifactId>
           <version>${mapstruct.version}</version>
       </dependency>
   </dependencies>

   <plugin>
       <groupId>org.jetbrains.kotlin</groupId>
       <artifactId>kotlin-maven-plugin</artifactId>
       <version>${kotlin.version}</version>
       <extensions>true</extensions>
       <executions>
           <execution>
               <id>kapt</id>
               <goals>
                   <goal>kapt</goal>
               </goals>
               <configuration>
                   <sourceDirs>
                       <sourceDir>src/main/kotlin</sourceDir>
                       <sourceDir>src/main/java</sourceDir>
                   </sourceDirs>
                   <aptMode>stubs</aptMode>
                   <annotationProcessorPaths>
                       <annotationProcessorPath>
                           <groupId>org.mapstruct</groupId>
                           <artifactId>mapstruct-processor</artifactId>
                           <version>${mapstruct.version}</version>
                       </annotationProcessorPath>
                   </annotationProcessorPaths>
               </configuration>
           </execution>
       </executions>
   </plugin>
  • 把 kotlin-maven-plugin 中 kapt goal 的执行添加在 compile 执行之前。
  • 使用 aptMode 选项配置注解处理的模式。

Gradle Kotlin

1
2
3
4
5
6
7
8
   plugins {
       kotlin("kapt") version "2.4.20"
   }

   dependencies {
       implementation("org.mapstruct:mapstruct:1.6.3")
       kapt("org.mapstruct:mapstruct-processor:1.6.3")
   }

Gradle Groovy

1
2
3
4
5
6
7
8
   plugins {
       id "org.jetbrains.kotlin.kapt" version "2.4.20"
   }

   dependencies {
       implementation "org.mapstruct:mapstruct:1.6.3"
       kapt "org.mapstruct:mapstruct-processor:1.6.3"
   }
  1. 定义你的数据类和映射器接口:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
   import org.mapstruct.Mapper
   import org.mapstruct.factory.Mappers

   data class UserDto(val id: Long, val firstName: String, val lastName: String)

   data class UserEntity(val id: Long, val firstName: String, val lastName: String)

   @Mapper
   interface UserMapper {
       fun toDto(entity: UserEntity): UserDto
       fun toEntity(dto: UserDto): UserEntity

       companion object : UserMapper by Mappers.getMapper(UserMapper::class.java)
   }
  1. 构建项目。MapStruct 会在生成的源文件目录中生成 UserMapperImpl 类。使用 UserMapper 伴生对象调用生成的实现:
1
2
3
4
5
6
   fun main() {
       val entity = UserEntity(id = 1L, firstName = "John", lastName = "Doe")
       val dto = UserMapper.toDto(entity)
       println(dto)
       // UserDto(id=1, firstName=John, lastName=Doe)
   }

在 Gradle 项目中使用 KSP

借助 KSP,你可以在 Gradle 项目中使用现有的注解处理器,并创建自己的、根据源代码中注解生成代码的处理器。

用 KSP 配合 Java 注解处理器

对于 Gradle 项目,请把 KSP 与兼容的注解处理器一起使用。KSP 比 kapt 更快,并且能原生理解 Kotlin 特有特性。请参阅已支持 KSP 的库列表。

下面的例子演示如何使用 Dagger,这是一个编译期依赖注入框架,会为你的依赖图生成装配代码。

  1. 在你的 build.gradle(.kts) 文件中应用 KSP 插件,并把 Dagger 添加到 dependencies 块:

Kotlin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
   // build.gradle.kts

   plugins {
       kotlin("jvm") version "2.4.20"
       id("com.google.devtools.ksp") version "2.3.10"
   }

   dependencies {
       implementation("com.google.dagger:dagger:2.59.2")
       ksp("com.google.dagger:dagger-compiler:2.59.2")
   }

Groovy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
   // build.gradle

   plugins {
       id 'org.jetbrains.kotlin.jvm' version '2.4.20'
       id 'com.google.devtools.ksp' version '2.3.10'
   }

   dependencies {
       implementation 'com.google.dagger:dagger:2.59.2'
       ksp 'com.google.dagger:dagger-compiler:2.59.2'
   }

提示: 要查找 KSP 的最新版本,请查看 GitHub 的 Releases 页面。

  1. 用 Dagger 注解标注你的 Kotlin 类:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   import javax.inject.Inject
   import javax.inject.Singleton
   import dagger.Component
   import dagger.Module
   import dagger.Provides

   @Singleton
   class UserRepository @Inject constructor() {
       fun getUser(): String = "John Doe"
   }

   @Module
   class AppModule {
       @Provides
       @Singleton
       fun provideUserRepository(): UserRepository = UserRepository()
   }

   @Singleton
   @Component(modules = [AppModule::class])
   interface AppComponent {
       fun userRepository(): UserRepository
   }
  1. 构建项目。Dagger 会生成实现类,例如 build/generated/ksp 目录中的 DaggerAppComponent。在代码中使用生成的类:
1
2
3
4
5
6
    fun main() {
        val appComponent = DaggerAppComponent.create()
        val userRepository = appComponent.userRepository()
        println("User: ${userRepository.getUser()}")
        // User: John Doe
    }

关于 Dagger 对 KSP 支持的更多信息,请参阅其文档。

创建你自己的注解处理器

你可以使用 KSP API 编写自己的注解处理器,在编译期生成代码。一个新的处理器需要三个模块:

  • 一个 annotation 模块,用于声明自定义注解。
  • 一个 processor 模块,用于实现 SymbolProcessor 和 SymbolProcessorProvider 工厂。SymbolProcessor 包含主要逻辑,而 SymbolProcessorProvider 负责创建处理器并在 META-INF/services/ 路径中注册提供者。
  • 一个 app 模块,应用 KSP 插件、依赖于该处理器并使用该注解。

完整的分步说明请参阅 KSP 快速入门。

接下来学什么