선생님, 개발을 잘하고 싶어요.

Kotlin in Action - 2부 10장 - 애노테이션과 리플렉션 본문

일상/책 리뷰

Kotlin in Action - 2부 10장 - 애노테이션과 리플렉션

알고싶은 승민 2022. 6. 6. 20:20

다루는 거

  • 애노테이션(annotation)
    • 라이브러리가 요구하는 의미를 클래스에게 부여
  • 리플렉션(reflection)
    • 실행 시점에 컴파일러 내부 구조를 분석

애노테이션

  • 메타데이터를 선언에 추가해 애노테이션을 처리하는 도구가 컴파일 시점이나 실행 시점에 적절한 처리
  • 애노테이션 인자로 원시 타입 값, 문자열, enum, 클래스 참조, 다른 애노테이션 클래스 그리고 이러한 배열이 들어갈 수 있다.
  • 애노테이션 인자는 컴파일 시점에 알 수 있어야한다.
  • 코틀린 한 소스코드의 선언을 컴파일한 결과가 여러 자바 선언과 대응하는 경우가 있다. (e.g. var로 선언한 property는 getter, setter에 대응된다)
  • 사용 지점 대상(use-site target) 선언으로 애노테이션을 붙일 요소를 정할 수 있다.
  • @get:Rule 에서
    • get → 사용 지점 대상
    • Rule → 애노테이션 이름
  • 사용 지점 대상 지원 목록
    • property: 프로퍼티 전체 - 자바에서 선언된 애노테이션에는 사용 불가
    • field: 프로퍼티에 의해 생성되는 backing 필드
    • get: 프로퍼티 게터
    • set: 프로퍼티 세터
    • receiver: 확장 함수나 프로퍼티 수신 객체 파라미터
    • param: 생성자 파라미터
    • setparam: 세터 파라미터
    • delegate: 위임 프로퍼티의 위임 인스턴스를 담아둔 필드
    • file: 파일 안에 선언된 최상위 함수와 프로퍼티를 담아두는 클래스
  • 코틀린은 자바 바이트코드로 컴파일하는 방법, 코틀린 선언을 자바에 노출하는 방법을 제어하기 위한 애노테이션 제공한다.
    • @Volatile
    • @Strictfp
    • @JvmName: 자바 필드, 메서드 이름 변경
    • @JvmStatic
    • @JvmOverloads: 디폴트 파라미터 값이 있는 함수에 대해 컴파일러가 자동으로 오버로딩한 함수 생성
    • @JvmField: getter, setter가 없는 public 자바 필드로 프로퍼티 노출

애노테이션 선언

annotation class JsonExclude
annotation class JsonName(val name: String) // 인자가 있는 경우
  • 오직 선언이나 식과 관련 있는 메타데이터의 구조를 정의하기 때문에 내부에 아무 코드도 없다.
  • 자바 애노테이션의 value 메서드는 특별한데, 애노테이션을 적용할 때, value를 제외한 모든 애트리뷰트 이름을 명시해야하기 때문이다.
  • 반면 코틀린 애노테이션은 일반적인 생성자 호출과 마찬가지 방법으로 애노테이션 적용이 가능하다.

메타 애노테이션

  • 컴파일러가 애노테이션을 처리하는 방법을 제어
  • @Target 애노테이션을 적용할 수 있는 요소의 유형을 지정한다.
    • 기본은 모든 선언에 적용 가능
    • 프로퍼티에만 애노테이션을 달고 싶으면?
    @Target(AnnotationTarget.PROPERTY)
    annotation class JsonExclude
    
    • 메타애노테이션을 만들고 싶으면?
    @Target(AnnotationTarget.ANNOTATION_CLASS)
    annotation class BindingAnnotation
    
  • @Retention 애노테이션 클래스를 소스 수준에서만 유지할지, .class 파일에 저장할지, 실행 시점에 리플렉션을 사용할 수 있게 할지 지정하는 메타애노테이션
    • 자바는 기본 .class 파일에 저장 but 런타임 사용 불가
    • 코틀린은 기본 RUNTIME 지정

애노테이션 파라미터로 클래스 사용 & 제네릭 클래스 활용

  • 어떤 클래스를 선언 메타데이터로 참조할 수 있는 기능이 필요할 때도 있다.
  • e.g. 역직렬화 시 어떤 클래스를 사용해 인터페이스를 구현할 것인가 지정
  • 클래스 받기 패턴
    • 클래스를 인자로 받는 경우
    // KClass<out Any> 사용
    annotation class DeseializeInterface(val targetClass: KClass<out Any>)
    
    • 제네릭 클래스를 인자로 받는 경우
    annotation class CustomSerializer(
      val serializerClass: KClass<out ValueSerializer<*>>
    )
    

리플랙션: 실행 시점에 코트린 객체 내부 관찰

  • 리플렉션은 실행 시점에(동적으로) 객체의 프로퍼티와 메서드에 접근할 수 있게 해주는 방법
    • 타입과 관계없이 객체를 다뤄야하는 경우
    • 객체가 제공하는 메서드나 프로퍼티 이름을 오직 실행 시점에만 알 수 있는 경우
  • 리플랙션을 사용하는 자바 라이브러리와 코틀린 코드가 완전히 호환된다. (java.lang.reflect)
  • 자바에 없는 프로퍼티, nullable 타입과 같은 코틀린 고유 개념에 대한 리플렉션 제공 (kotlin.reflect)

코틀린 리플렉션 API

  • KClass
    • 클래스를 표현한다.
    • java.lang.Class에 해당하는 KClass
    • 모든 선언 열거, 상위 클래스 얻는 등의 작업 가능
    class Person(val name: String, val age: Int)
    
    val person = Person("Seungmin", 27)
    
    val kClass = Person::class // 클래스로부터 KClass 얻기
    val kClass = person.javaClass.kotlin // 인스턴스로부터 KClass 얻기
    
  • KCallable
    • 함수, 프로퍼티의 공통 상위 인터페이스
    • call 인터페이스를 제공해 가변 인자와 가변 반환을 할 수 있다.
  • KFunction
    • 함수 표현
    • invoke 함수를 제공해서 컴파일 타임에 인자 개수와 타입에 대한 체크를 할 수 있다.
    • KFunction1<Int, Unit>: 이런 식으로 반환 값 타입 정보가 들어있는 식으로 활용 가능
    • KFunctionN은 컴파일러가 생성한 합성 타입이므로, 이런 타입의 정의를 미리 찾을 수 없을 것이다. 컴파일러가 원하는 만큼 생성하므로 kotlin-runtime.jar 크기를 줄일 수 있고 함수 파라미터 개수에 대한 인위적인 제약을 피한다.
  • KProperty
    • 프로퍼티 표현 (함수의 로컬 변수에는 접근할 수 없다)
    • get 함수를 제공해서 프로퍼티 값을 얻을 수 있다.
    • KProperty0 최상위 프로퍼티 → 인자 없는 get 함수로 값 받아오기
    • KProperty1 멤버 프로퍼티 → 객체에 속해 있는 프로퍼티이므로 값을 받아오려면 객체 인스턴스를 넘겨야한다. 즉 인자가 1개 있는 get함수 제공

JKid 에서 얻은 tips

  • KAnnotatedElement 인터페이스에는 annotations 프로퍼티가 있다. 해당 요소에 적용된 (@Retention이 RUNTIME으로 지정된) 모든 애노테이션 인스턴스의 컬렉션
    • 프로퍼티에 적용된 annotation을 받아오려면? property.annotations
  • 어떤 애노테이션을 찾으려면?
    • KAnnotatedElement.findAnnotation()
  • object로 선언한 instance를 받아오려면?
    • KClass.objectInstance
  • class의 인자 없거나 모든 인자가 optional한 객체를 생성하려면?
    • KClass.createInstance()
  • 디폴트 파라미터 값을 지원하고 싶으면?
    • KCallable.callBy()
    • Map<KParameter, Any?>를 인자로 넘겨야한다.
    • 인자 순서에 상관없이 함수를 호출할 수 있다.
  • 함수의 파라미터에 접근하려면?
    • KFunction.parameters: List<KParameter>
  • 함수 파라미터에 디폴트 값이 있는지 없는지 알려면?
    • KParameter.isOptional
    • true이면 디폴트 값이 있음
  • 타입이 nullable인지 아닌지 알려면?
    • KType.isMarkedNullable
Comments