Skip to content

Latest commit

ย 

History

History
221 lines (181 loc) ยท 9.23 KB

File metadata and controls

221 lines (181 loc) ยท 9.23 KB

Annotation Processor

Annotation์ด๋ž€?

Annotation์€ ์ž๋ฐ” ์†Œ์Šค์ฝ”๋“œ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ์˜ ํ•œ ํ˜•ํƒœ์ด๋‹ค.
ํด๋ž˜์Šค, ์ธํ„ฐํŽ˜์ด์Šค, ๋ฉ”์†Œ๋“œ, ๋ณ€์ˆ˜, ๋งค๊ฐœ๋ณ€์ˆ˜ ๋“ฑ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
Annotation์€ ์†Œ์Šค ํŒŒ์ผ์—์„œ ์ฝ์„ ์ˆ˜๋„ ์žˆ๊ณ , ์ปดํŒŒ์ผ๋Ÿฌ์— ์˜ํ•ด ์ƒ์„ฑ๋œ ํด๋ž˜์Šค ํŒŒ์ผ์— ๋‚ด์žฅ๋˜์–ด ์ฝํž ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ,
Runtime์— Java VM์— ์˜ํ•ด ์œ ์ง€๋˜์–ด ๋ฆฌํ”Œ๋ ‰์…˜์— ์˜ํ•ด ์ฝ์–ด๋‚ผ ์ˆ˜๋„ ์žˆ๋‹ค.

Annotation Processor๋ž€?

Annotation Processor๋Š” Java ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์ผ์ข…์œผ๋กœ ์ผ๋ฐ˜์ ์œผ๋กœ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๊ฒ€์‚ฌ, ์ˆ˜์ • ๋˜๋Š” ์ƒ์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

Annotation Processor ์žฅ์ 

  • ๋ชจ๋“  ์ฒ˜๋ฆฌ๊ฐ€ ๋Ÿฐํƒ€์ž„์ด ์•„๋‹Œ ์ปดํŒŒ์ผํƒ€์ž„์— ๋ฐœ์ƒํ•˜์—ฌ ๋น ๋ฅด๋‹ค
  • ๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค
  • ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค

Annotation Processor ๋™์ž‘

  1. ์ž๋ฐ” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค
  2. ์‹คํ–‰๋˜์ง€์•Š์€ Annotation Processor๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค
  3. ํ”„๋กœ์„ธ์„œ ๋‚ด๋ถ€์—์„œ Annotation์ด ๋‹ฌ๋ฆฐ Element(๋ณ€์ˆ˜, ๋ฉ”์†Œ๋“œ, ํด๋ž˜์Šค)๋“ฑ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค
  4. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ชจ๋“  Annotation Processor๊ฐ€ ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๊ทธ๋ ‡์ง€์•Š๋‹ค๋ฉด ๋ฐ˜๋ณตํ•ด์„œ ์œ„ ์ž‘์—…์„ ์‹คํ–‰ํ•œ๋‹ค

Annotation, Annotation Processor ๋งŒ๋“ค๊ธฐ

์„ค์ •

  1. annotation module ์ถ”๊ฐ€ํ•˜๊ธฐ
    annotation module

  2. annotation_processor module ์ถ”๊ฐ€ํ•˜๊ธฐ
    annotation processor module

  3. ์˜์กด์„ฑ ์„ค์ •
    app/build.gradle

dependencies {
    implementation project(':annotation')
    kapt project(':annotation_processor')
}

annotation_processor/build.gradle

dependencies {
    implementation project(':annotation')
}

Annotation, Annotation Processor ๋งŒ๋“ค๊ธฐ

1. Custom Annotation ์„ ์–ธ

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class PrintFunction
  • annotation 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 ๋ฅผ ๋งŒ๋“ค๋•Œ ์‚ฌ์šฉํ•œ๋‹ค

2. AnnotationProcessor ์ƒ์„ฑ

์• ๋…ธํ…Œ์ด์…˜๊ฐ€ ๋‹ฌ๋ฆฐ ๋ฉ”์†Œ๋“œ์˜ ํด๋ž˜์Šค์ด๋ฆ„, ๋ฉ”์†Œ๋“œ์ด๋ฆ„์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์ถœ๋ ฅํ•˜๋Š” 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() : ์ผ๋ฐ˜์ ์œผ๋กœ ์ตœ์‹ ์˜ ์ž๋ฐ” ๋ฒ„์ „์„ ๋ฆฌํ„ด

3. AnnotationProcessor ๋“ฑ๋ก

image
annotation_processor/src/main/resources/META-INF/services ์œ„์น˜์— javax.annotation.processing.Processor์„ ์ƒ์„ฑํ•˜๊ณ 
ํŒŒ์ผ์„ ์—ด์–ด ์• ๋…ธํ…Œ์ด์…˜ ํ”„๋กœ์„ธ์„œ์˜ ํŒจํ‚ค์ง€๋ช…์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” CanonicalName์„ ์ ๋Š”๋‹ค.
com.beomjo.sample.annotation_processor.PrintFunctionProcessor

์˜ˆ์ œ

AnnotationRetention.RUNTIME ์‚ฌ์šฉ

TBD

AnnotationRetention.BINARY ์‚ฌ์šฉ

Annotation ์„ ์–ธ

@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class GenerateWith

Annotation 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 SampleClass

app/build/generated/source/kaptKotlin/....์— Generated ํŒŒ์ผ ์ถ”๊ฐ€๋จ
image

ํ•˜๋“œ์ฝ”๋”ฉ ํ•˜์ง€์•Š๊ณ  kotlin-poet ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ๋„ ์ข‹์„๊ฒƒ ๊ฐ™๋‹ค.

AnnotationRetention.SOURCE ์‚ฌ์šฉ

PrintFunction ์–ด๋…ธํ…Œ์ด์…˜ ์ฐธ๊ณ