[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 속성으로 대입된다.