Kotlin 프로젝트에서 애너테이션 프로세서 사용하기
Kotlin 프로젝트에서 애너테이션 프로세서 사용하기 (Use annotation processors in Kotlin projects)
애너테이션 프로세서는 컴파일 타임에 소스 코드를 분석해서 반복적인 코드를 생성하거나, 사용법을 검증하거나, 다른 산출물을 만들어 내는 도구예요. Kotlin에서는 두 가지 방식으로 애너테이션 프로세서를 다룰 수 있습니다.
본문
애너테이션 프로세서는 컴파일 타임에 소스 코드를 분석해 반복 코드(boilerplate code)를 생성하거나, 사용법을 검증하거나, 기타 산출물을 만드는 도구입니다. Kotlin은 애너테이션 프로세서를 다루는 방법을 두 가지 제공해요.
-
kapt 컴파일러 플러그인은 Kotlin 소스 코드에서 스텁(stub) 파일을 생성한 뒤, 그 스텁에 Java 애너테이션 프로세서를 실행하는 방식으로 동작해요. 이 추가적인 스텁 생성 단계 때문에 빌드 시간이 느려지고, 확장 함수나 null 안전성 같은 Kotlin 고유 구조는 kapt가 이해하지 못합니다.
kapt는 Maven과 Gradle을 모두 지원해요. 모든 Maven 프로젝트와, MapStruct처럼 아직 KSP를 도입하지 않은 프로세서 라이브러리를 쓰는 Gradle 프로젝트에 권장됩니다.
-
KSP 프레임워크는 스텁을 생성하지 않고 Kotlin 우선(Kotlin-first) API를 통해 Kotlin 소스 코드를 직접 읽어요. Kotlin 고유 기능을 자연스럽게 이해하고, kapt보다 빌드를 더 빠르게 실행합니다.
현재 KSP는 공식적으로 Gradle만 지원해요. 자신만의 프로세서를 작성하거나 Dagger 같은 KSP 호환 라이브러리를 쓸 때 권장됩니다.
Java 애너테이션 프로세서와 함께 kapt 사용하기
kapt를 쓰면 기존 Java 애너테이션 프로세서를 프로세서 자체를 전혀 수정하지 않고 Kotlin 프로젝트에서 사용할 수 있어요.
아래 예시는 컴파일 타임에 Java 빈(bean) 사이의 타입 안전한 매퍼 구현을 생성하는 MapStruct 애너테이션 프로세서를 사용하는 방법을 보여 줍니다.
- 빌드 파일에서
kapt플러그인을 적용하고dependencies섹션에 MapStruct를 추가해요.
<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옵션으로 애너테이션 처리 방식을 설정합니다.
plugins {
kotlin("kapt") version "2.4.20"
}
dependencies {
implementation("org.mapstruct:mapstruct:1.6.3")
kapt("org.mapstruct:mapstruct-processor:1.6.3")
}
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"
}
- 데이터 클래스와 매퍼 인터페이스를 정의해요.
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)
}
- 프로젝트를 빌드해요. MapStruct가 생성 소스 디렉터리에
UserMapperImpl클래스를 생성합니다.UserMapper컴패니언 객체를 사용해서 생성된 구현을 호출할 수 있어요.
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 프로젝트에서 기존 애너테이션 프로세서를 쓸 수 있고, 소스 코드의 애너테이션을 기반으로 코드를 생성하는 나만의 프로세서도 만들 수 있어요.
Java 애너테이션 프로세서와 함께 KSP 사용하기
Gradle 프로젝트에서는 호환되는 애너테이션 프로세서와 함께 KSP를 사용하세요. KSP는 kapt보다 빠르고 Kotlin 고유 기능을 자연스럽게 이해합니다. 이미 KSP를 지원하는 라이브러리 목록을 확인해 보세요.
아래 예시는 의존성 그래프의 배선(wiring) 코드를 생성하는 컴파일 타임 의존성 주입 프레임워크인 Dagger를 사용하는 방법을 보여 줍니다.
build.gradle(.kts)파일에서 KSP 플러그인을 적용하고dependencies블록에 Dagger를 추가해요.
// 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")
}
// 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 페이지에서 확인할 수 있어요.
- Kotlin 클래스에 Dagger 애너테이션을 붙여요.
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
}
- 프로젝트를 빌드해요. Dagger가
build/generated/ksp디렉터리에DaggerAppComponent같은 구현 클래스를 생성합니다. 생성된 클래스를 코드에서 사용해 보세요.
fun main() {
val appComponent = DaggerAppComponent.create()
val userRepository = appComponent.userRepository()
println("User: ${userRepository.getUser()}")
// User: John Doe
}
Dagger의 KSP 지원에 대한 자세한 내용은 그 문서를 참고하세요.
나만의 애너테이션 프로세서 만들기
KSP API를 사용하면 컴파일 타임에 코드를 생성하는 나만의 애너테이션 프로세서를 작성할 수 있어요. 새 프로세서에는 세 개의 모듈이 필요합니다.
- 커스텀 애너테이션을 선언하는
annotation모듈 SymbolProcessor와SymbolProcessorProvider팩토리를 구현하는processor모듈.SymbolProcessor가 핵심 로직을 담당하고,SymbolProcessorProvider가 프로세서를 만들고META-INF/services/경로에 프로바이더를 등록해요.- KSP 플러그인을 적용하고 프로세서에 의존하며 애너테이션을 사용하는
app모듈
단계별 전체 지침은 KSP 퀵스타트를 참고하세요.