자바 Annotation

Jeongmin Yeo (Ethan)·2021년 2월 3일
4

STUDY HALLE

목록 보기
12/13
post-thumbnail

자바의 애노테이션에 대해 정리합니다.

백기선님과 함께하는 자바 12주차 스터디 과정입니다.

학습할 내용은 다음과 같습니다.

  • 애노테이션 정의하는 방법
  • @Retention
  • @Target
  • @Documented
  • 애노테이션 프로세서

References


1. 애노테이션 정의하는 방법

애노테이션이란 메타 데이터의 형태로 프로그램에 대한 정보를 제공해줍니다. 애노테이션이 붙어 있다고 해서 직접적인 코드의 영향을 주지는 않습니다.

애노테이션을 사용하는 UseCase는 다음과 같습니다.

  • Information for the compiler

    • 컴파일러는 애노테이션 정보를 바탕으로 에러를 탐지하거나 경고를 줄 수 있습니다.
  • Compile-time and deployment-time processing

    • 소프트웨어 툴은 애노테이션 정보를 이용해 코드나 XML 파일등을 생성할 수 있습니다.
  • Runtime processing

    • 일부 에노테이션은 런타임에서 검사할 수 있습니다.

The Format of an Annotation

애노테이션 중 가장 간단하고 유명한 예는 @Override가 있습니다.

이 에노테이션에서 @ 글자가 컴파일러에게 이건 Override라는 애노테이션이라는 정보를 제공해줍니다.

어떤 애노테이션들은 특정한 요소들을 포함할 수 있습니다.

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

or

@SuppressWarnings(value = "unchecked")
void myMethod() { ... }

만약 애노테이션이 포함할 수 있는 요소가 한 요소밖에 없다면 생략해서 쓸 수 있습니다. 그리고 @Override 처럼 아예 특정 요소가 없는 에노테이션도 있습니다.

@SuppressWarnings("unchecked")
void myMethod() { ... }

그 중 한 선언부에 여러 에노테이션을 사용하는게 가능합니다.

@Author(name = "Jane Doe")
@EBook
class MyClass { ... }

그리고 필요에 따라 같은 타입의 에노테이션을 여러개 사용할 수도 있습니다. 이렇게 사용하는 이유는 이후에 자세하게 설명하겠습니다.

@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

이런 애노테이션 타입은 java.lang or java.lang.annotation에서 제공해주는 것이 있고 필요에따라 자신이 정의해서 사용하는게 가능합니다.

Where Annotations Can Be Used

에노테이션은 클래스, 필드, 메소드 등 프로그램의 요소를 선언할 때 사용할 수 있습니다.

예는 다음과 같습니다.

Class instance creation expression:

  new @Interned MyObject(); 

Type cast:

myString = (@NonNull String) str;

implements clause:

class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }

Thrown exception declaration:

void monitorTemperature() throws @Critical TemperatureException { ... }

Declaring an Annotation Type

많은 에노테이션은 코드를 설명하는 주석 대신 사용해서 설명할 수 있습니다.

예를 들면 다음과 같은 클래스가 있고 그 클래스를 설명하는 주석이 있다고 가정해보겠습니다.

public class Generation3List extends Generation2List {

   // Author: John Doe
   // Date: 3/17/2002
   // Current revision: 6
   // Last modified: 4/12/2004
   // By: Jane Doe
   // Reviewers: Alice, Bill, Cindy

   // class code goes here
}

이 주석은 메타 데이터를 이용하는 애노테이션을 통해 표현할 수 있습니다.

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

애노테이션 정의는 인터페이스 정의와 유사합니다. 다만 인터페이스 앞에 @ 키워드가 더 붙습니다.

에노테이션 정의에서 body 부분에는 메소드 형태와 유사하게 보이는 에노테이션이 포함할 요소를 선언해주면 됩니다.

그리고 주목할 점은 특정 요소들을 선택적으로 기본 값을 정의할 수 있습니다.

애노테이션 정의를 완료했다면 사용할 땐 정의한 특정 요소들을 채워 넣으면 됩니다. 예시는 다음과 같습니다.

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

만약 이 에노테이션 정보를 javadoc 생성시 표현하고 싶다면 @Documented 에노테이션을 붙이면 됩니다. 실제 예시는 여기를 참조해 주세요.

Predefined Annotation Types

Java SE API에서는 미리 정의된 에노테이션들이 있습니다. 이런 에노테이션들은 컴파일러가 사용하기도 하고 다른 에노테이션에 사용되기도 합니다.

미리 정의된 에노테이션들은 다음과 같습니다.

java.lang @Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    String since() default "";
    
    boolean forRemoval() default false;
}

@Deprecated 에노테이션이 붙은 요소들은 자바 버전이 올라가면서 더 이상 사용하지 않는 것입니다.

만약 이 에노테이션이 붙은 메소드나 클래스 필드를 사용할려고 하면 컴파일러는 경고를 냅니다.

이 에노테이션에 붙은 @Retention과 @Target은 이후에 자세하게 설명하겠습니다.

@Deprecated에 있는 요소인 since는 자바의 특정 버전을 말합니다. 즉 이 버전부터 삭제되었다는 뜻입니다.

@Deprecated에 있는 요소인 forRemoval는 이 에노테이션이 붙은 요소가 이후에 삭제될 수 있는지 여부를 나타냅니다. 기본값은 false 입니다.

java.lang @Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override 에노테이션은 컴파일러에게 슈퍼 클래스가 정의한 요소를 재정의 하겠다고 알려줍니다.

메소드를 재정의할 때 이 요소를 반드시 사용하지 않아도 되지만 오류를 방지하는데 도움을 줍니다.

java.lang. @SuppressWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

@SuppressWarnings 에노테이션은 이 에노테이션이 없었다면 발생할 경고들을 억제하는데 사용하는 에노테이션입니다.

@SuppressWarnings 에노테이션에 있는 value는 억제할 경고 리스트를 말합니다.

다음 예제에서는 이 에노테이션 사용으로 인해 경고를 막아주는 예제입니다.

 // use a deprecated method and tell 
 // compiler not to generate a warning
 @SuppressWarnings("deprecation")
    void useDeprecatedMethod() {
        // deprecation warning
        // - suppressed
        objectOne.deprecatedMethod();
    }

or

@SuppressWarnings({"unchecked", "deprecation"})

컴파일러의 경고는 크게 두 종류 deprecation과 unchecked가 있습니다.

unchecked warning은 제네릭이 등장하기 이전에 쓰여진 레거시 코드의 인터페이스를 사용할 때 발생합니다.

이런 종류의 경고들을 막기 위해선 @SuppressWarnings를 사용하면 됩니다.

java.lang @SafeVarargs

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

@SafeVarargs은 생성자나 메소드 피러미터가 varargs가 안전하게 사용됨을 말해주는 에노테이션 입니다

java.lang @FunctionalInterface

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@FunctionalInterface는 자바 8부터 들어온 에노테이션이며 인터페이스가 abstract method가 하나만 있는 FunctionalInterface 임을 알려주는 에노테이션 입니다.

Annotations That Apply to Other Annotations

어노테이션은 또 다른 어노테이션에서 사용되는 어노테이션이 있습니다. java.lang.annotation에는 이런 어노테이션들을 지정하고 있습니다.

종류는 다음과 같습니다. 아래의 에노테이션은 이후에 자세하게 설명하겠습니다.

  • @Retention

  • @Documented

  • @Target

  • @Inherited

  • @Repeatable

Type Annotations and Pluggable Type Systems

자바 8 이전에 에노테이션들은 각 선언부에만 적용이 가능했습니다. 하지만 자바 8 이후부터는 어떠한 부분에서도 에노테이션은 적용할 수 있습니다.

이 말은 이전에도 소개했듯이 클래스 인스턴스의 생성, 타입 캐스트, implements 문, throws 문에 사용할 수 있습니다.

이러한 유형의 에노테이션을 Type Annotations이라고 합니다.

Type annotations은 자바 컴파일러와 함께 사용되는 하나 이상의 플러그형 모듈 사용으로 인해 프로그램에서 보다 강력한 타입 체킹을 보장해줍니다.

예를들면 어떠한 특정 변수가 반드시 널이 할당되지 않는다고 합시다. 그렇다면 NullPointerException을 피할 수 있습니다.

이런 정보를 이용해 다음과 같이 타입 체킹을 할 수 있습니다.

@NonNull String str;

이렇게 함으로써 NonNull 모듈을 감지해 코드를 컴파일 할 때 컴파일러는 잠재적인 문제를 감지한다면 경고를 출력해 오류를 방지할 수 있습니다.

이와 비슷한 여러 모듈을 사용해 서로 다른 유형의 오류를 컴파일 시점에 체크할 수 있습니다.

보다 더 다양한 checking modules을 확인하고 싶다면 워싱턴 대학에서 만든 Checker Framework를 확인해 보세요.

Repeating Annotations

때로는 동일한 에노테이션을 적용해야 할 때가 있습니다. 이건 자바 8 이후로 들어온 기능인 repeating annotations을 통해 가능합니다.

예를 들어 UNIX cron 서비스와 유사한 특정 시간 또는 특정 예약에 따라 메서드를 실행할 수 있는 타이머 서비스를 사용하기 위한 코드를 작성한다고 가정합니다.

이제 매월 마지막 날과 매주 금요일 오후 11시에 메소드를 실행하는 타이머를 설정하려고 합니다.

실행할 타이머를 설정하려면 @Schedule 에노테이션을 두 번 만들어서 doPeriodicCleanup 메소드에 적용해야합니다.

첫 번째 사용은 달의 마지막 날을 지정하고 두 번째 사용은 다음 코드 예제와 같이 금요일 오후 11시를 지정합니다.

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

모든 위치에서 동일한 에노테이션은 중복해서 사용 가능합니다.

그리고 호환성 때문에 repeating annotations은 Java 컴파일러에 의해 자동으로 생성되는 container annotation에 저장됩니다. 컴파일러가 이 작업을 수행하려면 코드에서 두 개의 선언이 필요합니다.

Step 1: Declare a Repeatable Annotation Type

이 에노테이션 타입은 @Repeatable meta-annotation을 적용시켜야 합니다. 방금 나온 예제인 @Schedule을 다음과 같이 지정합니다.

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

@Repeatable meta-annotation의 값은 자바 컴파일러가 repeating annotations을 저장하기 위해 생성하는 container annotation입니다.

이 예제에서 containing annotation type은 Schedules라고 지정했습니다. 그러므로 반복되는 @Schedule 에노테이션은 모두 @Schedules여기에 저장됩니다. 복수형을 잘 확인해주세요.

이와같이 하지 않고 그냥 에노테이션을 반복해서 적용한다면 컴파일 에러를 발생할 것입니다.

Step 2: Declare the Containing Annotation Type

그리고 containing annotation은 반드시 가져아 할 요소 이름으로는 value를 가져야 하고 타입으로는 배열이어야 합니다.

public @interface Schedules {
    Schedule[] value();
}

Retrieving Annotations

자바 Reflection API에는 에노테이션을 검색하는 메소드가 있습니다. 그 중 예로 AnnotedElement.getAnnotation() 메소드를 통해서 에노테이션을 가지고 올 수 있습니다.

이 메소드의 경우 오로지 한개의 에노테이션만을 리턴합니다. Repeating Annotations의 경우에는 containing annotation을 가지고 옵니다.

이런 방식을 통해 자바 8 이전에 썼던 레거시 코드는 문제 없이 작동합니다.

자바 8 이후 부터는 여러 에노테이션을 한꺼번에 반환하기 위해 AnnotatedElement.getAnnotationsByType() 메소드를 통해 가능합니다.


2. @Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Retention 에노테이션은 마크된 에노테이션이 얼마나 보존할 지를 결정해주는 에노테이션 입니다.

보존할 기간을 특정하게 명시하지 않으면 기본값은 클래스 파일까지 보존합니다.

RetentionPolicy.SOURCE

  • 마크된 에노테이션은 소스코드 레벨까지 유지되고 컴파일될 때 무시됩니다.

RetentionPolicy.CLASS

  • 마크된 에노테이션은 컴파일 단계까지 유지되고 JVM이 실행될 때 무시됩니다.

RetentionPolicy.RUNTIME

  • 마크된 에노테이션은 JVM이 실행될 때 즉 런타임 시점까지도 유지됩니다.

3. @Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

@Target 에노테이션은 이 에노테이션이 적용될 수 있는 컨택스트를 명시합니다.

즉 이 말은 @Target meta-annotation이 적용됐다면 컴파일러는 이 에노테이션의 요소 값인 ElementType을 보고 이 에노테이션의 적용 시킬 수 있는 범위를 제한시킵니다.

예를 들면 ElementType.ANNOTATION_TYPE 값인 경우 @Target 에노테이션은 다른 에노테이션에만 적용시킬 수 있습니다.

@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotationType {
        ...
}

@Target 에노테이션은 다음 중 하나의 값을 명시합니다.

ElementType.ANNOTATION_TYPE

  • 이 값을 가진 에노테이션은 에노테이션 선언에만 적용시킬 수 있습니다.

ElementType.CONSTRUCTOR

  • 이 값을 가진 에노테이션은 생성자 선언에만 적용시킬 수 있습니다.

ElementType.FIELD

  • 이 값을 가진 에노테이션은 필드 값이나 프로퍼티 값에만 적용시킬 수 있습니다.

ElementType.LOCAL_VARIABLE

  • 이 값을 가진 에노테이션은 로컬 변수에만 적용시킬 수 있습니다.

ElementType.METHOD

  • 이 값을 가진 에노테이션은 메소드에만 적용시킬 수 있습니다.

ElementType.PACKAGE

  • 이 값을 가진 에노테이션은 패키지에만 적용시킬 수 있습니다.

ElementType.PARAMETER

  • 이 값을 가진 에노테이션은 메소드 피라미터에만 적용시킬 수 있습니다.

ElementType.TYPE

  • 이 값을 가진 에노테이션은 어떠한 종류의 클래스에서 적용시킬 수 있습니다.

4. @Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented 에노테이션은 javadoc과 같은 툴로 인해 문서화 됨을 말합니다.


5. 애노테이션 프로세서

어노테이션 프로세싱은 컴파일 시간에 어노테이션들을 스캐닝하고 프로세싱하는 javac 에 속한 빌드툴입니다.

특정 어노테이션들을 위해 어노테이션 프로세서를 만들어서 등록할 수 있습니다.

어노테이션 프로세싱은 자바 5 부터 가능하지만 유용한 API 들은 자바 6 (2006년 12월에 출시) 부터 사용 가능합니다.

특정 어노테이션을 위한 어노테이션 프로세서는 자바 코드(또는 컴파일된 바이트 코드)를 인풋으로 받아서 아웃풋으로 파일(보통 .java 파일)을 생성합니다.

이게 의미하는 바는 자기가 자바 코드를 생성할 수 있음을 뜻합니다. 이미 존재하는 자바 파일을 수정해서 메서드를 추가하는 것은 할 수 없지만 새로 생성된 자바 파일은 javac 에 의해 컴파일 될 것입니다.

profile
좋은 습관을 가지고 싶은 평범한 개발자입니다.

0개의 댓글