Annotation

어노테이션은 잘만 사용하면 정말 유용한 Java의 구문이다.

기본적인 종류는 몇 가지에 한정되지만, 내가 원하는대로 커스텀 어노테이션을 만들어 사용할 수 있다.

 

어노테이션의 본질적인 목적은 소스 코드에 메타데이터를 표현하는 것.

하지만 단순히 부가적인 표현을 넘어, reflection을 이용하면 어노테이션 지정만으로 원하는 클래스를 주입하는 것도 가능하다.

 

Built-in Annotation

Java에서 기본적으로 제공하는 어노테이션은 다음과 같다.

  • @Override : 오버라이딩할 때 사용.
  • @Deprecated : 더이상 사용되지않는 메서드를 지정한다. 해당 어노테이션이 붙은 메서드를 사용했을 시 컴파일 경고.
  • @SuppressWarnings : 컴파일 경고를 무시한다.
  • @SafeVarargs : 제네릭과 같은 가변인자 파라미터를 사용할 때, 경고를 무시한다.
  • @FunctionalInterface : 람다 함수를 위한 인터페이스를 명시적으로 지정한다. 메서드가 없거나 두 개 이상이면 컴파일 에러 발생.

 

Meta Annotation

메타 어노테이션은 커스텀 어노테이션을 만들 때 사용된다.

메타 어노테이션의 종류는 다음과 같다.

  • @Retention : 해당 어노테이션이 저장될 범위를 지정한다.
                      코드단에서만 저장되고말지, 컴파일된 클래스 파일 안에도 저장되어야할지, 또는 런타임 내내 유지할지.
  • @Documented : 문서에 어노테이션의 정보를 포함한다.
  • @Target : 해당 어노테이션이 적용될 위치를 지정한다.
  • @Inherited : 자식 클래스가 해당 어노테이션을 상속받을 수 있게 한다.
  • @Repeatable : 반복적으로 해당 어노테이션을 선언할 수 있게 한다.

 

Declare Custom Annotation

Java에서 커스텀 어노테이션을 만드는 방법 자체는 간단하다.

public @Interface MyAnnotation {

}

 

위의 형식에서 메타 어노테이션을 붙여 기능을 추가해가며 만들면 된다.

@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 MyAnnotation {

    // enum 타입을 선언할 수 있다.
    public enum Color {RED, GREEN, BLUE}
    
    // String은 기본 자료형이 아니지만 사용이 가능하다.
    String value();
    
    // 배열 형태도 사용이 가능하다.
    int[] values();
    
    // enum 형태를 사용하는 방법
    Color color() default Color.RED;
}

 

Simple Example

@StringInjector 라는 커스텀 어노테이션을 만들어보자.

/**
 * String 문자열을 주입하기 위해 선언할 수 있는 어노테이션.
 * 필드에만 선언할 수 있고, JVM이 해당 어노테이션 정보를 참조한다.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StringInjector {

    String value() default "This is StringInjector.";
}

 

@StringInjector 어노테이션을 적용할 클래스는 다음과 같다.

@Data
public class MyObject {

    @StringInjector("My name is Pangtrue.")
    private String name;
    
    @StringInjector
    private String testText;
}

 

다음 코드는 어노테이션을 찾고, 주입하는 역할을 하는 컨테이너 클래스다.

@NoArgsConstructor
public class MyContextContainer {

    /**
     * 파라미터로 받은 클래스의 객체를 반환한다.
     */
    public <T> T get(Class clazz) throws IllegalAccessException, InstantiationException {
        T instance = (T) clazz.newInstance();
        instance = invokeAnnotations(instance);
        return instance;
    }
    
    private <T> T invokeAnnotations(T instance) throws IllegalAccessException {
        Field[] fields = instance.getClass().getDeclaredFields();
        
        for (Field field : fields) {
            StringInjector annotation = field.getAnnotation(StringInjector.class);
            
            if (annotation != null && field.getType() == String.class) {
                field.setAccessable(true);
                field.set(instance, annotation.value());
            }
        }
        
        return instance;
    }
}

 

위 예제 코드들의 실행을 담당할 메인 엔트리.

public class AnnotationDemo {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        MyContextContainer demo = new MyContextContainer();

        MyObject obj = demo.get(MyObject.class);

        System.out.println(obj.getName()); // print is "My name is Pangtrue."
        System.out.println(obj.gettestText()); // print is "This is StringInjector."
    }
}

 

 

참고 사이트

https://jdm.kr/blog/216