Annotation์ ์๋ฐ ์์ค์ฝ๋์ ์ถ๊ฐํ ์ ์๋ ๋ฉํ ๋ฐ์ดํฐ์ ํ ํํ์ด๋ค.
ํด๋์ค, ์ธํฐํ์ด์ค, ๋ฉ์๋, ๋ณ์, ๋งค๊ฐ๋ณ์ ๋ฑ์ ์ถ๊ฐํ ์ ์๋ค.
Annotation์ ์์ค ํ์ผ์์ ์ฝ์ ์๋ ์๊ณ , ์ปดํ์ผ๋ฌ์ ์ํด ์์ฑ๋ ํด๋์ค ํ์ผ์ ๋ด์ฅ๋์ด ์ฝํ ์๋ ์์ผ๋ฉฐ,
Runtime์ Java VM์ ์ํด ์ ์ง๋์ด ๋ฆฌํ๋ ์
์ ์ํด ์ฝ์ด๋ผ ์๋ ์๋ค.
Annotation Processor๋ Java ์ปดํ์ผ๋ฌ์ ํ๋ฌ๊ทธ์ธ์ ์ผ์ข ์ผ๋ก ์ผ๋ฐ์ ์ผ๋ก ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๊ฒ์ฌ, ์์ ๋๋ ์์ฑํ๋๋ฐ ์ฌ์ฉ๋๋ค.
- ๋ชจ๋ ์ฒ๋ฆฌ๊ฐ ๋ฐํ์์ด ์๋ ์ปดํ์ผํ์์ ๋ฐ์ํ์ฌ ๋น ๋ฅด๋ค
- ๋ฆฌํ๋ ์ ์ ์ฌ์ฉํ์ง ์๋๋ค
- ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ฅผ ์์ฑํด์ค๋ค
- ์๋ฐ ์ปดํ์ผ๋ฌ๊ฐ ์ปดํ์ผ๋ฌ๋ฅผ ์ํํ๋ค
- ์คํ๋์ง์์ Annotation Processor๋ฅผ ์ํํ๋ค
- ํ๋ก์ธ์ ๋ด๋ถ์์ Annotation์ด ๋ฌ๋ฆฐ Element(๋ณ์, ๋ฉ์๋, ํด๋์ค)๋ฑ์ ์ฒ๋ฆฌํ๋ค
- ์ปดํ์ผ๋ฌ๊ฐ ๋ชจ๋ Annotation Processor๊ฐ ์คํ๋์๋์ง ํ์ธํ๊ณ , ๊ทธ๋ ์ง์๋ค๋ฉด ๋ฐ๋ณตํด์ ์ ์์ ์ ์คํํ๋ค
-
์์กด์ฑ ์ค์
app/build.gradle
dependencies {
implementation project(':annotation')
kapt project(':annotation_processor')
}
annotation_processor/build.gradle
dependencies {
implementation project(':annotation')
}
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class PrintFunctionannotation class: ์ ๋ ธํ ์ด์ ํด๋์ค ์ ์ธ@Target: ์ ๋ ธํ ์ด์ ํ๊ฒ ์ง์ public enum class AnnotationTarget { CLASS, //class, interface, enum ๋ฑ์ ์ง์ ํ ๋ ANNOTATION_CLASS, //์ ๋ ธํ ์ด์ ํด๋์ค์ ์ง์ ํ ๋ TYPE_PARAMETER, // Generic type parameter์ ์ง์ ํ ๋ PROPERTY, // ํ๋กํผํฐ์ ์ง์ ํ ๋ FIELD, // property์ backing field์ ์ง์ ํ ๋ LOCAL_VARIABLE, // ๋ก์ปฌ ๋ณ์์ ์ ๋ ธํ ์ด์ ์ ์ง์ ํ ๋ VALUE_PARAMETER, // ํจ์ ๋๋ ์์ฑ์์ ๊ฐ ๋งค๊ฐ ๋ณ์์ ์ง์ ํ ๋ CONSTRUCTOR, // ์์ฑ์์ ์ ๋ ธํ ์ด์ ์ ์ง์ ํ ๋ FUNCTION, // ํจ์์ ์ ๋ ธํ ์ด์ ์ ์ง์ ํ ๋ PROPERTY_GETTER, // ํ๋กํผํฐ ๊ฒํฐ์ ์ง์ ํ ๋ PROPERTY_SETTER, // ํ๋กํผํฐ ์ธํฐ์ ์ง์ ํ ๋ TYPE, EXPRESSION, // ์์์ expression์ ์ง์ ํ ๋ (ex, @Suppress(โUNCHECKED_CASTโ)) FILE, //top-level function ์ด๋ property๋ฑ๋ฑ์ ์ง์ ํ ๋ @SinceKotlin("1.1") TYPEALIAS }
@Retention: ์ฌ์ฉ์ ์ ์ ์ ๋ ธํ ์ด์ ์ด ์ ์ฅ๋๋ ํ์ ์ ๋ด๋ฉฐ 3๊ฐ์ง ํ์ ์ด ์๋คAnnotationRetention.BINARY- CompileTime ๊ณผ Binary ์๋ ํฌํจ๋์ง๋ง Reflection ์ ํตํด ์ ๊ทผํ ์๋ ์๋ค
AnnotationRetention.SOURCE- CompileTime ์๋ง ์ ์ฉํ๋ฉฐ ๋น๋๋ Binary ์๋ ํฌํจ๋์ง ์๋๋ค.
- ๊ฐ๋ฐ์ค์ warning ์ด ๋จ๋ ๊ฑธ ๋ณด์ด์ง ์๋๋ก ํ๋
@suppress์ ๊ฐ์ด ๊ฐ๋ฐ ์ค์๋ง ์ ์ฉํ๋ค - Binary ์ ํฌํจ๋ ํ์๋ ์๋ ๊ฒฝ์ฐ์ ํ๋ค
AnnotationRetention.RUNTIME- CompileTime๊ณผ Binary ์๋ ํฌํจ๋๋ค
- Reflection ์ ํตํด ์ ๊ทผ ๊ฐ๋ฅํ๋ค
@Retention์ ํ์ํด์ฃผ์ง ์์๊ฒฝ์ฐ, ๋ํดํธ๋ก RUNTIME์ผ๋ก ์ง์ ๋๋ค
@MustBeDocumented- Generated Documentation ์ ํด๋น Annotation ๋ ํฌํจ๋ ์ ์๋์ง๋ฅผ ๋ํ๋ธ๋ค
- ์ฃผ๋ก Library ๋ฅผ ๋ง๋ค๋ ์ฌ์ฉํ๋ค
์ ๋ ธํ ์ด์ ๊ฐ ๋ฌ๋ฆฐ ๋ฉ์๋์ ํด๋์ค์ด๋ฆ, ๋ฉ์๋์ด๋ฆ์ ๊ฐ๋ตํ๊ฒ ์ถ๋ ฅํ๋ Annotation Processor ์์ฑ
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class PrintFunctionProcessor : AbstractProcessor() {
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
//ํ๋ก์ธ์ฑ์ ํ์ํ ๊ธฐ๋ณธ์ ์ธ ์ ๋ณด๋ค์ processingEnvironment ๋ถํฐ ๊ฐ์ ธ์จ๋ค
}
override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
val elements = p1?.getElementsAnnotatedWith(PrintFunction::class.java)
elements?.forEach {
StringBuilder()
.append("---------------\n")
.append("enclosedElements: ${it.enclosedElements}\n")
.append("enclosingElement: ${it.enclosingElement}\n")
.append("kind: ${it.kind}\n")
.append("modifiers: ${it.modifiers}\n")
.append("simpleName: ${it.simpleName}\n")
.let(::println)
}
return false
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return hashSetOf(PrintFunction::class.java.canonicalName) // ์ด๋ค ์ ๋
ธํ
์ด์
์ ์ฒ๋ฆฌํ ์ง Set์ ์ถ๊ฐ
}
override fun getSupportedSourceVersion(): SourceVersion {
return super.getSupportedSourceVersion() //์ง์๋๋ ์์ค ๋ฒ์ ์ ๋ฆฌํด
}
}- init() : ํ์ผ์ ์์ฑํ๊ธฐ ์ํด ํ์ํ Filer๋ ๋๋ฒ๊น ์ ํ์ํ Messager, ๊ฐ์ข ์ ํธํด๋์ค๋ค์ ์ด๊ณณ์์ ๋ฐ์ ์ ์๋ค
- process() : ํ๋ก์ธ์์ ํต์ฌ ์ผ๋ก ์ด๊ณณ์์ ํด๋์ค, ๋ฉ์๋, ํ๋ ๋ฑ์ ์ถ๊ฐํ ์ ๋ ธํ ์ด์ ์ ์ฒ๋ฆฌํ๊ณ ์ฒ๋ฆฌ์ ๋ํ ๊ฒฐ๊ณผ๋ก ์๋ฐ ํ์ผ์ ์์ฑํ ์ ์๋ค
- getSupportedAnnotationType() : ์ด๋ค ์ ๋ ธํ ์ด์ ๋ค์ ์ฒ๋ฆฌํ ์ง Setํํ๋ก ๋ฆฌํดํ๊ฒ ๋๋ค
- getSupportedSourceVersion() : ์ผ๋ฐ์ ์ผ๋ก ์ต์ ์ ์๋ฐ ๋ฒ์ ์ ๋ฆฌํด

annotation_processor/src/main/resources/META-INF/services ์์น์ javax.annotation.processing.Processor์ ์์ฑํ๊ณ
ํ์ผ์ ์ด์ด ์ ๋
ธํ
์ด์
ํ๋ก์ธ์์ ํจํค์ง๋ช
์ ํฌํจํ๊ณ ์๋ CanonicalName์ ์ ๋๋ค.
com.beomjo.sample.annotation_processor.PrintFunctionProcessor
TBD
Annotation ์ ์ธ
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class GenerateWithAnnotation Processor ๊ตฌํ
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class ClassGeneratorProcessor : AbstractProcessor() {
private val kaptKotlinGeneratedOption = "kapt.kotlin.generated"
private lateinit var kaptKotlinGenerated: File
private val sampleAnnotation = GenerateWith::class.java.canonicalName
override fun getSupportedAnnotationTypes() = setOf(sampleAnnotation)
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
kaptKotlinGenerated = File(processingEnv.options[kaptKotlinGeneratedOption])
}
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
val annotation = annotations.firstOrNull { it.toString() == sampleAnnotation } ?: return false
for (element in roundEnv.getElementsAnnotatedWith(annotation)) {
val className = element.simpleName.toString()
val `package` = processingEnv.elementUtils.getPackageOf(element).toString()
generateClass(className, `package`)
}
return true
}
private fun generateClass(className: String, `package`: String) {
val source = generateSourceSource(`package`, className)
val relativePath = `package`.replace('.', File.separatorChar)
val folder = File(kaptKotlinGenerated, relativePath).apply { mkdirs() }
File(folder, "Generated$className.kt").writeText(source)
}
private fun generateSourceSource(`package`: String, className: String) =
"""
package $`package`
class Generated$className {
fun printInfo() {
println("The annotated class was $`package`.$className")
}
}
""".trimIndent()
}์์ฑํ Annotation ์ถ๊ฐ
@GenerateWith
class SampleClassapp/build/generated/source/kaptKotlin/....์ Generated ํ์ผ ์ถ๊ฐ๋จ

ํ๋์ฝ๋ฉ ํ์ง์๊ณ kotlin-poet ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ๋ ์ข์๊ฒ ๊ฐ๋ค.


