title=자바 어노테이션(Java Annotations) created=2015-11-13

Annotation

어노테이션Annontion은 잘만 쓰면 정말 유용한 자바의 구문입니다. 기본적인 종류는 몇가지에 한정 되지만 본인의 입맛대로 커스텀 어노테이션Custom Annotation도 만들어낼 수 있습니다. 따라서 어노테이션의 종류는 무궁무진하게 만들어 낼 수 있습니다.

어노테이션은 본질적인 목적은 소스 코드에 메타데이터를 표현하는 것입니다. 단순히 부가적인 표현뿐만 아니라 리플렉션reflection을 이용하면 어노테이션 지정만으로도 원하는 클래스를 주입한다는지 하는 것이 가능합니다.

Bulit-in Annotation

자바에서는 기본적으로 제공하는 어노테이션들이 존재합니다.

  • @Override - 메소드가 오버라이드 됐는지 검증합니다. 만약 부모 클래스 또는 구현해야할 인터페이스에서 해당 메소드를 찾을 수 없다면 컴파일 오류가 납니다.
  • @Deprecated - 메소드를 사용하지 말도록 유도합니다. 만약 사용한다면 컴파일 경고를 일으킵니다.
  • @SuppressWarnings - 컴파일 경고를 무시하도록 합니다.
  • @SafeVarargs - 제너릭 같은 가변인자 매개변수를 사용할 때 경고를 무시합니다. (자바7 이상)
  • @FunctionalInterface - 람다 함수등을 위한 인터페이스를 지정합니다. 메소드가 없거나 두개 이상 되면 컴파일 오류가 납니다. (자바 8이상)

자세한 내용은 원문을 확인해 주세요.

Annotations applied to Java code:
@Override - Checks that the method is an override. Causes a compile error if the method is not found in one of the parent classes or implemented interfaces.
@Deprecated - Marks the method as obsolete. Causes a compile warning if the method is used.
@SuppressWarnings - Instructs the compiler to suppress the compile time warnings specified in the annotation parameters.
@SafeVarargs - Suppress warnings for all callers of a method or constructor with a generics varargs parameter, since Java 7.
@FunctionalInterface - Specifies that the type declaration is intended to be a functional interface, since Java 8.
https://en.wikipedia.org/wiki/Java_annotation

Meta Annotations

위에서 본 기본 어노테이션 외에도 메타 어노테이션Meta Annotation들이 있습니다. 이 메타 어노테이션을 이용해 커스텀 어노테이션을 만들어낼 수 있습니다.

  • @Retention - 어노테이션의 범위(?)라고 할 수 있겠습니다. 어떤 시점까지 어노테이션이 영향을 미치는지 결정합니다.
  • @Documented - 문서에도 어노테이션의 정보가 표현됩니다.
  • @Target - 어노테이션이 적용할 위치를 결정합니다.
  • @Inherited - 이 어노테이션을 선언하면 자식클래스가 어노테이션을 상속 받을 수 있습니다.
  • @Repeatable - 반복적으로 어노테이션을 선언할 수 있게 합니다.

자세한 내용은 원문을 확인해 주세요.

Annotations applied to other annotations (also known as "Meta Annotations"):

@Retention - Specifies how the marked annotation is stored—Whether in code only, compiled into the class, or available at runtime through reflection.
@Documented - Marks another annotation for inclusion in the documentation.
@Target - Marks another annotation to restrict what kind of Java elements the annotation may be applied to.
@Inherited - Marks another annotation to be inherited to subclasses of annotated class (by default annotations are not inherited to subclasses).
@Repeatable - Specifies that the annotation can be applied more than once to the same declaration, since Java 8.
https://en.wikipedia.org/wiki/Java_annotation

Declare Custom Annontation

자바에서 커스텀 어노테이션을 선언하는 방법은 간단합니다. 다음처럼만 지정해주면 됩니다.

public @interface MyAnnonation {}

아주 심플한 커스텀 어노테이션입니다.

이제 여기에 입맛대로 몇가지 메타 어노테이션들을 선언해주면 됩니다. 다음은 이것저것 다가져다 붙인 코드입니다.

import java.lang.annotation.*;

@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 Quality {BAD, GOOD, VERYGOOD}
    /* String은 기본 자료형은 아니지만 사용 가능합니다. */
    String value();
    /* 배열 형태로도 사용할 수 있습니다. */
    int[] values();
    /* enum 형태를 사용하는 방법입니다. */
    Quality quality() default Quality.GOOD;
}

설명이 필요할 것 같은 부분은 주석으로 대체했습니다.

Simple Example

다음 예제는 @StringInjector라는 간단한 커스텀 어노테이션을 만듭니다. 이 어노테이션은 멤버 변수에 선언시 해당 멤버 변수 타입이 String이라면 어노테이션에 정의된 값을 멤버 변수에 주입합니다.

아래 코드는 어노테이션을 선언하는 것을 보여줍니다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * String 문자열을 주입하기 위해 선언하는 어노테이션입니다.
 * FIELD에만 선언가능하고 JVM이 어노테이션 정보를 참조합니다.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StringInjector {
    String value() default "This is StringInjector.";
}

아래 코드는 어노테이션을 적용할 클래스를 보여줍니다.

public class MyObject {
    @StringInjector("My name is JDM.")
    private String name;

    @StringInjector
    private String defaultValue;

    @StringInjector
    private Integer invalidType;

    public String getName() {
        return name;
    }
    public String getDefaultValue() {
        return defaultValue;
    }
    public Integer getInvalidType() {
        return invalidType;
    }
}

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

import java.lang.reflect.Field;

public class MyContextContainer {

    public MyContextContainer(){}

    /**
     * 객체를 반환하기 전 어노테이션을 적용합니다.
     * @param instance
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    private <T> T invokeAnnonations(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.setAccessible(true);
                field.set(instance, annotation.value());
            }
        }
        return instance;
    }

    /**
     * 매개변수로 받은 클래스의 객체를 반환합니다.
     * @param clazz
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public <T> T get(Class clazz) throws IllegalAccessException, InstantiationException {
        T instance = (T) clazz.newInstance();
        instance = invokeAnnonations(instance);
        return instance;
    }
}

아래 코드는 위의 코드들을 전부 통합해 만든 테스트 코드입니다.

public class AnnotationDemo {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        // 컨텍스트 컨테이너를 초기화 합니다.
        MyContextContainer demo = new MyContextContainer();

        // MyOjbect 객체를 하나 받아옵니다.
        MyObject obj = demo.get(MyObject.class);

        System.out.println(obj.getName()); // print is "My name is JDM."
        System.out.println(obj.getDefaultValue()); // print is "This is StringInjector."
        System.out.println(obj.getInvalidType()); // print is "null".
    }
}

Closing Remarks

이제 남발하지 않는 한도 내에서 적당한 커스텀 어노테이션을 만들고 활용해봅시다. :)