본문 바로가기

Java/Java 기초

Java Custom Annotation

Java Annotation은 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종이다.

자바1.5 버전 이상에서 사용이 가능하며, @ 기호를 앞에 붙여서 사용한다.

Annotation은 클래스 파일에 임베디드되어 컴파일러에 의해 생성된 후 JVM에 포함되어 작동한다.

 

Annotation을 사용하면 관련 코드에 직접 메타데이터를 설정할 수 있어 가독성이 증대되며, 개발 효율성을 증대시킨다.

 

Java에서는 사전에 정의된 Annotation들이 존재하는데 @Deprecated, @Override, @suppressWarning이 그것이다. 이러한 Annotation 외에 Annotation 정의를 위해 사용되는 Annotation이 존재하는데 이를 Meta Annotation이라고 한다.

 

대표적으로 @Target, @Retention 등이 존재하는데 @Target은 Annotation의 사용 가능한 대상을 지정하며, ElementType의 값으로 지정된다. 

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** Field declaration (includes enum constants) */
    FIELD,
    /** Method declaration */
    METHOD,
    /** Formal parameter declaration */
    PARAMETER,
    /** Constructor declaration */
    CONSTRUCTOR,
    /** Local variable declaration */
    LOCAL_VARIABLE,
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    /** Package declaration */
    PACKAGE,
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

 

@Retention은 Annotation 정보의 유지 범위를 설정하는 것으로 RetentionPolicy에 의해 정의된다.

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,
    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,
    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

 

interface는 @interface로 선언이 가능하며, 이번에 만들 Annotation은 field 값이 양의 정수인지 확인하는 Annotation 이기 때문에 이름은 PositiveInteger로 명명했다.

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PositiveInteger {
	String message() default "This value has to be greater than 0.";
}

@Target 값으로는 ElementType.FIELD를 준 것은 사용범위를 필드 값으로 정의한 것이고, @Retention은 RetentionPolicy.RUNTIME를 사용하여 런타임에서 작동하도록 하였다. PositiveInteger는 message라는 값을 가질 수 있는데, default 값을 입력하였다. 만일 default 값을 입력하지 않았다면, Annotation을 사용하였을 때, message 값을 강제로 입력하도록 컴파일러 에러가 발생할 것이다. 물론 default가 존재하는 경우에는 따로 입력하지 않아도 되며, 입력하였을 경우, 값이 변경될 것이다.

 

이제 정의된 Annotation은 Class의 필드 값에 사용할 수 있다. 예시적으로 아래와 같은 형태가 된다.

public class TestObject {
	@PositiveInteger
	private Integer a;
	private Integer b;

	public TestObject(Integer a, Integer b) {
		this.a = a;
		this.b = b;
	}
}

a에는 Annotation이 적용되었지만 b에는 Annotation이 적용되지 않았기 때문에, 객채를 생성할 때 a에는 음수가 들어가서는 안되며, b에는 어떤 정수도 값도 허용될 것이다. 

 

import java.lang.reflect.Field;

public class PositiveIntegerAnnotator {
	public static <T> void checkPositive(T t) throws IllegalArgumentException, IllegalAccessException{
		for (Field field : t.getClass().getDeclaredFields()) {
			PositiveInteger annotation = field.getAnnotation(PositiveInteger.class);
			if (annotation != null && field.getType() == Integer.class) {
				field.setAccessible(true);
				if ((Integer) field.get(t) <= 0) {
					throw new RuntimeException(annotation.message());
				}
			}
		}
	}
}

PositiveIntegerAnnotator는 어떤 객체에 대해서 그 객체의 field를 검사하고 field에 PositiveInteger Annotation이 있는 경우 이를 검증하는 로직을 구현한 클래스이다. 이것이 가능한 것은 Java reflection의 getAnnotation을 통하여 Annotation 정보를 가져올 수 있기 때문이다. 위 로직에 따라 PositiveInteger Annotation을 사용한 필드는 0보다 작은 값을 갖게 되었을 때, Annotation에 입력되었거나, default로 입력된 메시지와 함께 RuntimeException을 발생시키게 된다. 

 

public class AnnotationTest {
	public static void main(String[] args) throws Exception {
		final TestObject to = new TestObject(-1, -1);
		PositiveIntegerAnnotator.checkPositive(to);
	}
}

위 코드에서 TestObject에 a, b 모두 음수를 넣었기 때문에 아래와 같은 메시지와 함께 RuntimeException이 발생하였다.

Exception in thread "main" java.lang.RuntimeException: This value has to be greater than 0.
	at PositiveIntegerAnnotator.checkPositive(PositiveIntegerAnnotator.java:10)
	at AnnotationTest.main(AnnotationTest.java:4)

 

참고자료

자바 애너테이션

https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EC%95%A0%EB%84%88%ED%85%8C%EC%9D%B4%EC%85%98

구루비 Dev 스터디

http://wiki.gurubee.net/display/DEVSTUDY/Annotation