Custom Annotation 만들기
Gson 이라는 클래스를 쓸때 @SerializedName("fieldName") 라는 쓰는 경우가 있고
이러한 Annotation의 원리가 궁금했다.
1. interface앞에 @를 붙여서 Annotation Interface를 선언한다.
2. Annotation을 구현한다. (Annotation 사용자의 경우)
3. Reflection을 이용하여 객체나 class에서 선언된 필드나 메소드를 찾고 적절한 동작을 구현한다.
의 순서로 접근하면 된다.
1. interface앞에 @를 붙여서 Annotation Interface를 선언한다.
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능합니다.
//@Retention(RetentionPolicy.CLASS) // 컴파일러가 클래스를 참조할 때까지 유효합니다.
//@Retention(RetentionPolicy.SOURCE) // 어노테이션 정보는 컴파일 이후 없어집니다.
@Target({
ElementType.PACKAGE, // 패키지 선언시
ElementType.TYPE, // 타입 선언시
ElementType.CONSTRUCTOR, // 생성자 선언시
ElementType.FIELD, // 멤버 변수 선언시
ElementType.METHOD, // 메소드 선언시
ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
ElementType.PARAMETER, // 매개 변수 선언시
ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
ElementType.TYPE_USE // 타입 사용시
})
public @interface CustomAnnotation {
String name() default "default";
String value() default "default";
}
- name(), value()가 attribute명이자 getter가 된다.
- default는 필드값을 안채우거나 하나만 쓸때 채워지는 값이다.
- interface위의 필드(Target, Retention 등)는 주석을 보거나 실생활에 쓰이고 있는 case를 참고하는것이 좋겠다.
1-1. 사용사례
@Override |
package java.lang; ... @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } |
@SerializedName |
package com.google.gson.annotations; ... @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface SerializedName { String value(); } |
@Test | package org.junit; ... @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Test { Class<? extends Throwable> expected() default Test.None.class; long timeout() default 0L; public static class None extends Throwable { private static final long serialVersionUID = 1L; private None() { } } } |
- Override 주석을 보면 그 유명한 Effective Java의 저자 조슈아 블로치(Joshua Bloch)이고 Java 1.5부터 반영된것으로 보인다!!
2. Annotation을 구현한다. (Annotation 사용자의 경우)
public class CustomAnnotationImpl {
@CustomAnnotation("aaa")
private String name;
@CustomAnnotation(value="ccc", name="bbb")
private String value;
CustomAnnotationImpl(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@CustomAnnotation("print")
public void printNameAndValue() {
System.out.println("name=" + name + ", value=" + value);
}
}
- 1에서처럼 매개변수가 여러개로 선언했으나 @CustomAnnotation("aaa")인 경우는 매개변수 선언 순서상의 마지막에 할당되는 것 같다.
3. Reflection을 이용하여 객체나 class에서 선언된 필드나 메소드를 찾고 적절한 동작을 구현한다.
Field 찾기 |
for (Field field : fieldArray) {
String fieldName = field.getName();
CustomAnnotation testAnnotation = field.getAnnotation(CustomAnnotation.class);
Annotation[] annotationArray = field.getDeclaredAnnotations();
boolean isAnnotationPresent = field.isAnnotationPresent(CustomAnnotation.class);
if (testAnnotation != null) {
field.setAccessible(true);
if (obj != null) {
String fieldValue = (String)field.get(obj);
field.set(obj, "33");
fieldValue = (String)field.get(obj);
}
}
} |
Method 찾기 |
for (Method method : methodArray) {
String methodName = method.getName();
CustomAnnotation testAnnotation = method.getAnnotation(CustomAnnotation.class);
Annotation[] annotationArray = method.getDeclaredAnnotations();
boolean isAnnotationPresent = method.isAnnotationPresent(CustomAnnotation.class);
if (testAnnotation != null) {
if (obj != null) {
method.invoke(obj);
}
}
} |
- 하지만 Reflection은 성능이 좋지 않다는 치명적인 단점이 있다. 매개변수의 타입체킹도 런타임에 되서 위험할 수도 있고...
잘 쓰면 괜찮다고는 하지만 그만큼 적절한가를 따져야 한다.
위 내용에 대한 코드는 github에서 확인하실 수 있습니다.
- https://github.com/ndukwon/Duk_Java_library/tree/master/src/com/duk/lab/java/annotation
Reference:
- http://jdm.kr/blog/216
- https://medium.com/@ggikko/java-%EC%BB%A4%EC%8A%A4%ED%85%80-annotation-436253f395ad
- https://www.javatpoint.com/custom-annotation