[Java] Custom Annotation 커스텀 어노테이션 만들기(reflection 리플렉션)

자바에서 커스텀 어노테이션을 만드는 방법은 다음과 같다.

public @interface MyAnnotation { }

사실상 인터페이스 키워드에 @를 붙이면 되는 것인데. 이것을 곧바로 클래스, 필드, 메서드 같은 곳에 붙이면 어노테이션으로써의 껍데기 역할은 할 수 있게 된다.

 

- @MyAnnotation 사용 예)

@MyAnnotation
public class MyMain{
	public static void main(String[] args){
    	System.out.println("Hello World!");
    }
}

 

@Retention 어노테이션

- Book 클래스

package org.example;

@MyAnnotation
public class Book {
    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(){

    }

    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;
    }

}

그런데 다음과 같이 Book이라는 클래스에 @MyAnnotation을 달고 리플렉션으로 어노테이션을 가지고 오고 싶을 때, 현재의 구조로는 가져올 수 없다.

Arrays
	.stream(Book.class.getDeclaredAnnotations())
	.forEach(System.out::println); 

위의 코드 결과를 main 메소드로 돌려보면 아무것도 출력되지 않는 것을 볼 수 있다.

-> 이유 : 어노테이션을 주석과 같은 취급을 받기 때문에 정보가 클래스에까지는 남지만 바이트 코드를 로딩하고 난 후 메모리에는 어노테이션의 정보는 가지고 오지 않는다.

-> 메모리에도 적재하기 위해서는 어노테이션 클래스에 @Retention(RetentionPolicy.RUNTIME) 어노테이션을 붙여야 한다.

public static void main( String[] args )
{
	Arrays.stream(Book.class.getDeclaredAnnotations()).forEach(System.out::println);
    // @org.example.MyAnnotation()
}

 

@Target 어노테이션

또한 위의 예에서는 모든 곳에 사용할 수 있게 된다. 즉, 필드, 메소드, 클래스 등 모든 곳에 어노테이션을 붙일 수 있게 되는데 어노테이션 사용을 허가하는 target을 설정할 수 있다.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})//사용할 위치 (타입, 필드)
public @interface MyAnnotation {
}

- 필드와 타입값에만 어노테이션을 허용하였고 Book 클래스에 이를 적용해보았다.

 

package org.example;

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

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

    private String a = "a";

    public String d = "d";

    protected String e = "e";

    @MyAnnotation // '@MyAnnotation' not applicable to constructor 컴파일에러
    public Book(){

    }
    
    //'@MyAnnotation' not applicable to parameter 컴파일에러.
    public Book(@MyAnnotation String a, String d, String e) {
        this.a = a;
        this.d = d;
        this.e = e;
    }

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

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

    public int h(){
        return 100;
    }

}

- 생성자와 파라미터에 @MyAnnotation을 붙이니 에러가 나는 것을 확인할 수 있다.

- 하지만 target으로 설정한 클래스타입과 필드에는 유효하게 적용된다.

 

어노테이션 필드

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})//사용할 위치 (타입, 필드)
public @interface MyAnnotation {
    String value() default "value"; //얘는 명시할 필요가 없음.
    String name () default "woosunghwan";
    int number() default 100; //Integer는 안됨
}

어노테이션에는 다음과 같이 필드같은 속성들을 선언할 수 있다.

예를 들어 @RequestMapping("/api/test") 와 같이 어노테이션에 괄호 안에 들어가는 값을 넣는 경우를 보았을 것이다. 이는 @RequestMapping이라는 어노테이션의 속성으로 값을 대입해주는 것이라고 생각해주면 되겠다.

 

대입은 다음과 같이 할 수 있다.

@MyAnnotation(name = "blackdog", number = 20)

위와 같이 Book 클래스 상단에 어노테이션을 다시 정의하고 main 메서드를 다음과 같이 실행하면

public static void main( String[] args )
{
	Arrays.stream(Book.class.getDeclaredAnnotations()).forEach(System.out::println);
} // 실행결과 : @org.example.MyAnnotation(name=blackdog, number=20, value=value)

default로 설정한 값이 아닌 어노테이션으로 파라미터를 전달하여 대입한 값이 나오는 것을 확인할 수 있다.

 

그런데 @RequestMapping의 경우 uri를 설정할 때 두개의 모습으로 사용하는 것을 볼 수 있었을 것이다.

@RequestMapping("/api/test")
@RequestMapping(value = "/api/test")

위의 두가지 어노테이션 사용은 결과는 모두 같다.

결론적으로 말하자면 어노테이션에서 속성 필드명을 value로 설정하게 된다면 굳이 어노테이션을 사용할 때 파라미터에 value라고 명시하지 않아도(단 파라미터가 하나일 때) 넘어가는 값이 value 속성으로 대입된다.

 

 

댓글

Designed by JB FACTORY