[Java] 애노테이션

JM·2022년 9월 18일
1

Java_Live_Study

목록 보기
12/15
post-thumbnail

학습할 것 (필수)

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

참고자료



애노테이션의 이점

  • Compiler instructions : 컴파일러에게 명령을 내릴 수 있다. 예를 들어 에러 또는 warning 을 감지하도록 할 수 있다. @Deprecated , @Override , @SuppressWarnings 와 같은 어노테이션들이 이러한 목적을 수행한다.
  • Compile-time instructions : 빌트툴이 자바 파일, XML 파일이나 그 외 파일들을 생성하도록 명령을 내려줄 수 있다.
  • Runtime instructions : 프로그램이 동작하는 중에 명령을 내릴 수 있다. 이때 해당 어노테이션은 자바 리플렉션을 통해 접근되어진다.



애노테이션 정의하는 방법

자바 어노테이션은 java element 에 메타데이터를 부여하는 방법이다. 자바 컴파일러와 JVM은 어노테이션이 제공해준 메타데이터를 활용한다.
예를 들어, 스프링에서 @Component 어노테이션이 적용된 클래스에 대해 해당 인스턴스를 빈으로 등록한다. 특정 어노테이션을 가지고 있으면, 그 어노테이션에 해당하는 동작을 수행하는 것이다.
즉, 어노테이션은 추가적인 정보를 제공하기 때문에, XML이나 java marker interface를 대신할 수 있다.

어노테이션에는 Standard(Built in) AnnotationCustom Annotation이 있다. 먼저, Standard annotation은 자바에 내장된 기본 에노테이션이다.

  • Standard Annotation

    • General Purpose Annotations

      • @Override : overriding 메소드가 부모 클래스에 있는 메소드인지 확인

        class ParentClass{
            void method(){}
        }
        
        public class OverrideTest extends ParentClass {
            @Override
            void method(){}
            @Override
            void method2(){}
            public static void main(String[] args) {}
        }
        
        // 컴파일 에러 발생
        // 부모 클래스에 method2()라는 메소드가 없기 때문에
        // java: method does not override or implement a method from a supertype
      • @Deprecated : marker annotation. declaration이 구식이며, 새로운 방식이 생겼음을 나타냄.

      • @SafeVarArgs : Heap pollution을 무시함. Generic과 관련한 사항.

      • @SuppressWarnings : 컴파일 중 특정 warning 이 발견되어도 warning 으로 뜨지 않게 한다.

        아래 경우는 어노테이션을 적용하지 않은 경우이다. 하단 이미지를 보면 deprecated된 메소드를 사용했다고 warning이 발생한 것을 알 수 있다.

        아래 경우는 어노테이션이 적용된 경우이다. deprecation 에 대한 warning 이 진압된 것을 알 수 있다.

      • @FunctionalInterface : 적용된 인터페이스가 함수형 인터페이스 조건에 부합하는 지 검사. 함수형 인터페이스란 추상 메소드가 하나인 인터페이스를 말한다. default 메소드가 static 메소드를 여러 개 존재해도 상관없다.

    • Meta Annotations

      • @Inherited : 자식 클래스에서 부모 클래스에 적용된 어노테이션을 상속받음
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Inherited
        @interface CustomAnnotation{
            String value();
        }
        
        @CustomAnnotation("hello!")
        class ParentTestClass{}
        
        public class InheritedTest extends ParentTestClass {
            public static void main(String[] args) {
                Class childClass = new InheritedTest().getClass();
                Annotation childAnnotation = childClass.getAnnotation(CustomAnnotation.class);
                Class parentClass = new ParentTestClass().getClass();
                Annotation parentAnnotation = parentClass.getAnnotation(CustomAnnotation.class);
                System.out.println(childAnnotation);
                System.out.println(parentAnnotation);
            }
        }
        
        // 출력
        // @com.jm.geeksforgeeks.CustomAnnotation(value=hello!)
        // @com.jm.geeksforgeeks.CustomAnnotation(value=hello!)
      • @Documented : 하단 설명
      • @Target : 하단 설명
      • @Retention : 하단 설명
      • @Repeatable : 동일한 위치에 같은 어노테이션 여러 개 적용가능
  • Custom Annotation

    어노테이션을 정의하기 위해 다음과 같은 문법을 따른다.

    public @interface Annotation{
        String value() default "default";
    }

    @interface 를 붙여 인터페이스임을 명시하고, Annotation 내부에서 갖고 있을 변수을 지정한다. default 키워드를 통해 변수에 대한 기본값을 명시할 수 있다.



@Retention

@Retentionretention policies 를 어노테이션에 적용시키는 메타 어노테이션이다. retention policy 란 해당 어노테이션이 어느 시점까지 적용되는 지를 말한다. 즉, 어노테이션의 라이프사이클을 지정한다고 말할 수 있다. retention policy 의 종류는 다음과 같다.(RetentionPolicy는 enum이다.)

  • RetentionPolicy.SOURCE : 이 정책이 적용된 어노테이션은 runtime에 폐기되며, 클래스 파일에 어노테이션이 저장되지 않는다.
    // 예시 : lombok의 @Getter
    
    @Target({ElementType.FIELD, ElementType.TYPE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Getter {
      ...
    }
    
    // 자바파일이 컴파일 될 때 Getter 메소드를 추가해주면 역할이 끝난다.
    // 따라서, 클래스 파일에는 어노테이션이 저장되지 않는다.
  • RetentionPolicy.CLASS : 이 정책이 적용된 어노테이션은 컴파일 이후 클래스 파일에 저장된다. 하지만, runtime에 폐기된다. CLASS는 default retention policy이다.
    // 예시 : lombok의 @NonNull
    
    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.CLASS)
    @Documented
    public @interface NonNull {
    }
    
    // @NonNull은 해당 값이 null인지 체크해준다. @NonNull이 적용된 
    // 라이브러리를 사용 시, IDE는 ClassFile에 적용된 어노테이션을
    // 확인하고, 힌트를 줄 것이다. 만약, SOURCE를 적용한다면,
    // 클래스파일에 어노테이션이 저장되지 않기 때문에,
    // 정상 작동하지 않을 것이다.
  • RetentionPolicy.RUNTIME : 이 정책이 적용된 어노테이션은 runtime에도 유지되며, 프로그램이 동작하는 중에도 해당 어노테이션에 접근할 수 있다.
    // 예시 : 스프링의 @Component
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Indexed
    public @interface Component {
      // ...
    }
    
    // 런타임에 스프링 프레임워크는 @Component가 적용된 클래스를
    // Bean으로 등록할 것이다.
@Retention(RetentionPolicy.SOURCE)
@interface SourceRetention{
    String value() default "Source Retention";
}

@Retention(RetentionPolicy.CLASS)
@interface ClassRetention{
    String value() default "Class Retention";
}

@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeRetention{
    String value() default "Runtime Retention";
}

@SourceRetention
class A{}

@ClassRetention
class B{}

@RuntimeRetention
class C{}

public class RetentionPolicyTest {
    public static void main(String[] args) {
        // Obtaining the array of annotations used to
        // annotate class A, B, and C. Array a and b will be
        // empty as their annotation are attached before
        // runtime while array c will contain the
        // RuntimeRetention annotation as it was marked with
        // RUNTIME retention policy
        Annotation a[] = new A().getClass().getAnnotations();
        Annotation b[] = new B().getClass().getAnnotations();
        Annotation c[] = new C().getClass().getAnnotations();

        // Printing the number of retained annotations of each class at runtime
        System.out.println("Number of annotations attached to class A at Runtime: " + a.length);
        System.out.println("Number of annotations attached to class B at Runtime: " + b.length);
        System.out.println("Number of annotations attached to class C at Runtime: " + c.length);
    }
}

// 출력
// Number of annotations attached to class A at Runtime: 0
// Number of annotations attached to class B at Runtime: 0
// Number of annotations attached to class C at Runtime: 1

위 예제의 결과와 같이, runtime에도 유지되는 retention policy는 RetentionPOLICY.RUNTIME이 유일하다.

class A {
    A() {
    }
}

@ClassRetention
class B {
    B() {
    }
}

@RuntimeRetention
class C {
    C() {
    }
}

위 코드는 컴파일된 바이트코드를 자바코드 형태로 변환된 모습이다. RetentionPolicy.SOURCE 의 적용을 받은 class A 는 컴파일 된 이후 어노테이션이 사라졌으며, 나머지 두 클래스의 어노테이션은 클래스 파일에서도 유지된다.



@Target

@Target 은 메타 어노테이션이다. 이 어노테이션은 ElementType 이라는 enum만을 매개변수로 사용한다. ElementType 은 프로그램 요소의 타입을 특정하는 상수를 담은 enum이다. 만약 @Target 어노테이션을 특정 ElementType 과 함께 사용한다면, 이 메타 어노테이션이 적용된 어노테이션은 ElementType 과 일치하는 element에만 적용할 수 있다. 그렇지 않을 경우, 에러를 발생할 것이다.

만약, 어노테이션에 @Target 이 적용되있지 않다면, 해당 어노테이션은 어디서든 적용 가능할까? 이는 자바 버전마다 다르다고 한다. 따라서, 명시적으로 target을 지정해야할 것 같다.

Element TypeElement to be Annotated
TypeClass, interface or enumeration
FieldField
MethodMethod
ConstructorConstructor
Local_VariableLocal variable
Annotation_typeAnnotation Type
PackagePACKAGE
Type_ParameterType Parameter
ParameterFormal Parameter
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassAnnotation{
    String value() default "Can annotate a class";
}

@Target({ElementType.METHOD,ElementType.TYPE,
        ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@interface MultipleElementTypeAnnotation{
    String value() default "Can annotate a class, method, annotation, or constructor";
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldElementTypeAnnotation{
    String value() default "Can annotate a field";
}

@ClassAnnotation
public class TargetTest {

    @MultipleElementTypeAnnotation
    public void myMethod(){}

    // java: annotation type not applicable to this kind of declaration
    // @FieldElementTypeAnnotation
    // public void errorOccurMethod(){}

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        TargetTest targetTest = new TargetTest();

        Annotation a[] = targetTest.getClass().getAnnotations();

        System.out.println(a[0]);

        Class<?> className = Class.forName("com.jm.geeksforgeeks.TargetTest");
        Annotation b[] = className.getMethod("myMethod").getAnnotations();
        System.out.println(b[0]);
    }
}

// 출력
// @com.jm.geeksforgeeks.ClassAnnotation(value=Can annotate a class)
// @com.jm.geeksforgeeks.MultipleElementTypeAnnotation(value=Can annotate a class, method, annotation, or constructor)



@Documented

커스텀 어노테이션이 자바 문서상에 보여질려면 @Documented 어노테이션을 적용시켜야 한다. @Documented 어노테이션은 메타 어노테이션이다.

@Documented 어노테이션의 효과를 알기 위해, 어노테이션 사용 전과 사용 후의 차이를 비교하겠다.

  • @Documented 어노테이션 사용 전
    @interface NotDocumentedAnnotation{
        String value();
    }
    
    @NotDocumentedAnnotation("NotDocumentedAnnotationTest")
    public class NotDocumentedAnnotationTest {
        public static void main(String[] args) {
            System.out.println("This is main method");
        }
    }
  • @Documented 어노테이션 사용 후
    @Documented
    @interface DocumentedAnnotation{
        String value();
    }
    
    @DocumentedAnnotation("DocumentedAnnotationTest")
    public class DocumentedAnnotationTest {
        public static void main(String[] args) {
            System.out.println("This is the main function");
        }
    }



애노테이션 프로세서

어노테이션 프로세스는 자바 코드가 컴파일 될 때, 추가적인 파일들을 생성해주는 테크닉을 말한다. 예를 들어, QueryDSL과 JPA에서 어노테이션 프로세서를 사용하여 metaclass를 생성한다. 이때 생성되는 파일들을 자바 파일 이외에 다른 파일일 수도 있다. 또한, 어노테이션 프로세스는 파일을 수정하는 것이 아닌, 새롭게 생성하는 것만 가능하지만, 예외적으로 Lombok 라이브러리에서는 자바 소스 파일을 수정한다.

자바 컴파일러는 소스파일에 있는 어노테이션을 조사하고 해당 어노테이션에 맞는 어노테이션 프로세서를 선택한다. 각각의 어노테이션 프로세서는 관련된 자바 소소파일에 의해서 호출된다.

(어노테이션 프로세스는 위와 같은 일련의 과정을 말하고, 어노테이션 프로세서는 어노테이션이 있을 때 그에 부합하는 행위를 수행하는 대상을 말한다.)

어노테이션 프로세싱 API는 javax.annotation.processing 에 정의되어 있다. 만약, 어노테이션 프로세스를 만들고 싶다면 해당 패키지에 있는 ProcessorInterface 를 구현해야 한다.

profile
나는 사는데로 생각하지 않고, 생각하는데로 살겠다

0개의 댓글