본문 바로가기
코틀린

코틀린에서 자주 사용하는 어노테이션(Annotation)@ 정리-2

by 기계공학 주인장 2023. 3. 11.
반응형

코틀린에서 자주 사용하는 어노테이션(Annotation)@ 정리-2

이번 포스팅에서는 저번 포스팅에 이어서 Annotation을 커스텀하는 방법을 알아보겠습니다.

 

 

 

코틀린에서 자주 사용하는 어노테이션(Annotation)@ 정리-1

코틀린에서 자주 사용하는 어노테이션에 대해 알아본다 어노테이션은 메타데이터 ( 부가기능 )을 코드에 비침투적으로 추가할 수 있는 수단이다 코틀린에서 어노테이션(Annotation)의 종류 Kotlin에

android-developer.tistory.com


Reflection을 사용한 Custom Annotation

코틀린에서 어노테이션을 커스텀하기 위한 방법으로는 두 가지 방법이 있습니다.

  1. Reflection을 사용한 커스텀
  2. Code Generation을 사용한 커스텀

먼저 Reflection이 뭔지 간단히 설명하자면

Reflection이란, 런타임 시 프로그램의 구조(객체, 함수, 프로퍼티)를 분석하는 기법입니다.

즉, 프로그램이 실행중일 때 인스턴스 등을 통해 객체의 내부 구조 등을 파악할 수 있습니다.

대신 이렇게 매번 확인하기 때문에 앱의 성능 저하를 일으킬 수 있습니다.

 

코틀린에서 Reflection을 사용하기 위해선 KClass를 사용해야 합니다.

 

코틀린 리플렉션(Reflection)에 대한 정보는 여기서 확인할 수 있습니다.

 

안드로이트 코틀린 Reflection(리플렉션) 기초 정의

코틀린에서 Reflection이란 런타임에 프로그램의 클래스를 조사하기 위해서 사용되는 기술입니다. 즉, 프로그램이 실행중일 때 인스턴스 등을 통해 객체의 내부 구조 등을 파악할 수 있습니다. 대

android-developer.tistory.com

 

코틀린에서 KClass를 사용하기 위해선 아래와 같은 라이브러리를 추가해줘야 합니다.

implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

 

그리고 다음과 같이 Annotation을 지정해서 annotation class를 만들어보겠습니다.

@Target(AnnotationTarget.PROPERTY)
annotation class StringConstraint(
    val minLength: Int,
    val maxLength: Int
)

위에서 생성한 StringContraint 어노테이션 클래스는 String 변수의 최소 길이, 최대 길이를 정의하기 위해 만든 어노테이션 클래스입니다.

 

해당 커스텀 어노테이션을 프로퍼티 값을 갖는 data class에 적용합니다.

 

data class Data(
    // set field value with custom annotation
    @StringConstraint(10, 50)
    val title: String,
    @StringConstraint(100, 500)
    val contents: String
)

 

이제 이 둘을 사용하여 Validation을 확인하는 코드를 작성합니다.

 

object FieldValidator {
    data class ValidationResult(
        var isValid: Boolean = true,
        val invalidFieldNames: MutableList<String> = mutableListOf()
    )

    fun validate(data: Data): ValidationResult {
        val result = ValidationResult()

        // get property information with reflection
        data::class.declaredMemberProperties.forEach {
            val field = it
            val annotation = it.findAnnotation<StringConstraint>()

            if (annotation != null && field.getter.visibility == KVisibility.PUBLIC) {
                val fieldValue = field.getter.call(data) as String
                // set validation with annotation field value
                if (fieldValue.length !in annotation.minLength..annotation.maxLength) {
                    result.isValid = false
                    result.invalidFieldNames.add(field.name)
                }
            }
        }

        return result
    }
}

 

그리고 다음과 같은 코드를 작성하여 실행합니다.

 

fun main() {
    val data = Data(
        "극도의 투명함",
        "투명함은 단순히 결과가 잘 공유되는 것 이상을 의미합니다. 우리는 결과뿐 아니라 의도와 맥락, 과정까지도 알 수 있을 만큼 공유하는 것을 투명하다고 합니다. 우리가 추구하는 극도의 솔직함은 투명함이 전제되어야만 발현된다고 믿습니다."
    )

    val validationResult = FieldValidator.validate(data)
    
    // Validation: false  /  not validated Field: [title]
    println("Validation: ${validationResult.isValid}  /  not validated Field: ${validationResult.invalidFieldNames}")
}

 


정리

이렇게 커스텀 Annotation과 코틀린 Reflection을 사용하는 방법에 대해 알아봤습니다.

 

참고로 모든 코드는 다음과 같습니다.

 

import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation

@Target(AnnotationTarget.PROPERTY)
annotation class StringConstraint(
    val minLength: Int,
    val maxLength: Int
)

data class Data(
    // set field value with custom annotation
    @StringConstraint(10, 50)
    val title: String,
    @StringConstraint(100, 500)
    val contents: String
)

object FieldValidator {
    data class ValidationResult(
        var isValid: Boolean = true,
        val invalidFieldNames: MutableList<String> = mutableListOf()
    )

    fun validate(data: Data): ValidationResult {
        val result = ValidationResult()

        // get property information with reflection
        data::class.declaredMemberProperties.forEach {
            val field = it
            val annotation = it.findAnnotation<StringConstraint>()

            if (annotation != null && field.getter.visibility == KVisibility.PUBLIC) {
                val fieldValue = field.getter.call(data) as String
                // set validation with annotation field value
                if (fieldValue.length !in annotation.minLength..annotation.maxLength) {
                    result.isValid = false
                    result.invalidFieldNames.add(field.name)
                }
            }
        }

        return result
    }
}

fun main() {
    val data = Data(
        "극도의 투명함",
        "투명함은 단순히 결과가 잘 공유되는 것 이상을 의미합니다. 우리는 결과뿐 아니라 의도와 맥락, 과정까지도 알 수 있을 만큼 공유하는 것을 투명하다고 합니다. 우리가 추구하는 극도의 솔직함은 투명함이 전제되어야만 발현된다고 믿습니다."
    )

    val validationResult = FieldValidator.validate(data)

    // Validation: false  /  not validated Field: [title]
    println("Validation: ${validationResult.isValid}  /  not validated Field: ${validationResult.invalidFieldNames}")
}

다음 포스팅에서는 두 번째 Annotation 커스텀 방법인 

 

Compile Time에 Code Generation을 활용해 Annotation에 로직을 삽입

에 대해 알아보겠습니다!

 

참조: https://blog.gangnamunni.com/post/kotlin-annotation/

반응형


"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."


댓글