반응형

@Retention

public @interface MyAnnotation {

}

기본적으로 애노테이션은 주석과 마찬가지로 취급한다.

  • .class 까지는 애노테이션 정보가 남지만 클래스 로딩 후에 메모리에는 애노테이션 정보가 남지 않는다.
  • 런타임 까지도 애노테이션 정보를 유지하고 싶다면 @Retention(RetentionPolicy.RUNTIME) 설정을 애노테이션 위에 추가해주어야 한다.
  • 기본값은 @Retention(RetentionPolicy.CLASS) 이다.
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

}

 

 

.class 파일에 들어있는 애노테이션 정보는 다음 명령을 통해 확인할 수 있다.
  • javap -c -v .class파일의 경로
    • F:\all_workspace\the_java_workspace\refactoring-example\target\classes\me\devhist ory\Book.class

 

  • @Retention(RetentionPolicy.CLASS) 설정 애노테이션은 런타임에 볼 수 없고 아래와 같이 표시된다.
    .
    . (생략)
    .
    SourceFile: "Book.java"
    RuntimeInvisibleAnnotations:
      0: #58()
        me.devhistory.MyAnnotation

 

  • @Retention(RetentionPolicy.RUNTIME) 설정을 추가하면 런타임에 애노테이션 정보를 볼 수 있으며 아래와 같이 표시된다.
    .
    . (생략)
    .
    SourceFile: "Book.java"
    RuntimeVisibleAnnotations:
      0: #58()
        me.devhistory.MyAnnotation

 

  • 런타임에서 볼 수 없는 애노테이션은 리플렉션 API를 사용해도 조회가 되지않는다.
    • 메모리에 애노테이션 정보가 없기 때문이다.
    Arrays.stream(Book.class.getAnnotations()).forEach(System.out::println);
    //조회 정보 없음
  • 런타임에서 볼 수 있는 애노테이션은 리플렉션 API를 이용해서 값을 조회할 수 있다.
    Arrays.stream(Book.class.getAnnotations()).forEach(System.out::println);
    //@me.devhistory.MyAnnotation()

 

@Target

@Target을 설정하게 되면 애노테이션을 사용할 수 있는 위치를 제한할 수 있다.

  • @Target({ElementType.TYPE, ElementType.FIELD})
    • TYPE, FIELD 이외의 영역에서 해당 애노테이션을 사용하게되면 컴파일 에러 발생.
@MyAnnotation
public class Book {
    @MyAnnotation private static String B= "BOOK";

    private static final String C= "BOOK";

    private String a = "a";

    public String d = "d";

    protected String e = "e";

    public Book() {
    }

//  @MyAnnotation 컴파일 에러 발생
    public Book(String a, String d, String e) {
        this.a = a;
        this.d = d;
        this.e = e;
    }

    private void f(){
        System.out.println("F");
    }

    public void g(){
        System.out.println("g");
    }

    public int h(){
        return 100;
    }

}

 

애노테이션 엘리먼트로 올 수 있는 타입은 다음과 같다.

  • 기본형(primitive type)
  • 문자열(String)
  • 클래스
  • 열거 타입(enum)
  • 애노테이션
  • 위의 타입으로 구성된 배열

 

애노테이션에 엘리먼트가 있고 기본값이 설정되어 있다면, 애노테이션 사용 시 꼭 값을 넘겨줄 필요는 없다. 인자를 전달받지않으면 기본값으로 설정된다.

  • @MyAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
    String name() default "devhistory";
    int number() default 100;
}

 

그러나 애노테이션에서 값을 선언해서 사용하는데 기본값이 설정되지 않은 경우라면 해당 애노테이션(MyAnnotation)을 사용할 때 값을 반드시 넘겨주어야 한다.

  • @MyAnnotation(name = "devhistory", number = 100)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
    String name();
    int number();
}

만약 애노테이션에 선언된 값 중 이름이 value() 인 것은 애노테이션 사용 시 이름을 명시적으로 적어주지 않아도 된다.

  • @MyAnnotation(value = "devhistory")
  • @MyAnnotation("devhistory")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
    String value() default "devhistory";
    int number() default 100;
}

그러나 여러 개의 속성을 설정하는 경우에는 이름이 value() 라도 명시적으로 이름을 작성해야 한다.

  • @MyAnnotation(value = "devhistory", number = 100)

 

@Inherited

부모 클래스에서 사용한 애노테이션 정보를 자식에게도 상속할 수 있도록 할 수 있다.

 

MyBook 클래스는 @Target 에서 보았던 Book 클래스를 상속하고 있다.

@MyAnnotation("devhistory2")
public class MyBook extends Book implements MyInterface{
}

그리고 MyAnnotation 애노테이션을 수정하여 상속 되도록 하였다.

@Inherited

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Inherited
public @interface MyAnnotation {
    String value() default "devhistory";
    int number() default  100;
}
  • 클래스에 정의된 애노테이션(상속받은 애노테이션 포함) 조회getAnnotations()
    • 부모에서 상속받은 애노테이션과 현재 클래스에 정의된 애노테이션이 같다면 현재 클래스의 애노테이션만 표시된다.
    Arrays.stream(MyBook.class.getAnnotations()).forEach(System.out::println);
    //@me.devhistory.MyAnnotation(value="devhistory2", number=100)
    • 부모에서 상속받은 애노테이션과 현재 클래스에 정의된 애노테이션이 다르다면 모두 표시된다.
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD})
    @Inherited
    public @interface AnotherAnnotation {
        String value() default "devhistory";
        int number() default  100;
    }
    @AnotherAnnotation("another")
    public class MyBook extends Book implements MyInterface{
    }
    Arrays.stream(MyBook.class.getAnnotations()).forEach(System.out::println);
    //@me.devhistory.MyAnnotation(value="devhistory", number=100)
    //@me.devhistory.AnotherAnnotation(value="another", number=100)
    • 현재 클래스에 정의된 애노테이션(자기 자신에만 붙어있는 애노테이션) 조회getDeclaredAnnotations()
Arrays.stream(MyBook.class.getDeclaredAnnotations()).forEach(System.out::println);
//@me.devhistory.AnotherAnnotation(value="another", number=100)

 

필드 애노테이션 조회

@MyAnnotation
public class Book {
    @MyAnnotation private static String B= "BOOK";

    @AnotherAnnotation private static final String C= "BOOK";

    private String a = "a";

    public String d = "d";

    protected String e = "e";

    @MyAnnotation
    public Book() {
    }

    @MyAnnotation
    public Book(String a, String d, String e) {
        this.a = a;
        this.d = d;
        this.e = e;
    }

    private void f(){
        System.out.println("F");
    }

    public void g(){
        System.out.println("g");
    }

    public int h(){
        return 100;
    }

}

 

특정 애노테이션이 붙어있는 필드를 확인할 수 있고, 그 필드에 붙어있는 애노테이션의 값을 꺼내어 사용할 수도 있다.

Arrays.stream(Book.class.getDeclaredFields()).forEach(f ->{
    Arrays.stream(f.getAnnotations()).forEach(a ->{
        if(a instanceof AnotherAnnotation) {
            AnotherAnnotation myAnnotation = (AnotherAnnotation) a;
            System.out.println(myAnnotation.value());
            System.out.println(myAnnotation.number());
        }
    });
});

//devhistory
//100
반응형

+ Recent posts