Lacti's Archive

java annotation을 사용한 InstanceId 구현

March 02, 2011

Java에서 생성하는 Object마다 InstanceId를 부여하는 방법에 대해 고민해보고 코드를 작성해보자.

Index 발급 Group 관련해서 파일을 하나 작성하여 IndexGeneratingManager를 구성해서 발급해도 되고, 아니면 발급 받는 시점을 적절히 조절하거나 발급 함수의 인자로 Group할 Class 정보를 넣어줘도 되겠다.
하지만 본 글에서는 Java 1.5부터 추가된 Annotation을 사용하여 source code에 metadata를 추가하여 문제를 해결하는 방법을 써 보겠다.

Java의 Annotation은 특정 지점(ElementType: Type, Method, Field, …)에 특정 시점(Retention: Source, Class, Runtime)까지 유지되는 metadata이다.
@interface keyword로 정의할 수 있고, 지정된 지점에 맞게 class, method, field, parameter 등에 선언될 수 있고, 이는 지정된 시점까지 유지된다.

위 문제를 해결하기 위해 새로 작성하는 IndexCategory라는 annotation은 index를 발급할 category class를 지정한다.

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface IndexCategory {
    public Class<?> category();
}

이 annotation은 Type에 선언될 수 있는데, 여기서 Typeclass를 뜻한다.
또한 Runtime까지 유지되는 정보인데, 그 이유는 객체가 “생성되는 시점”인 runtime에 해당 정보를 통해서 어떤 category의 class type으로 index를 발급받을지 얻어내야하기 때문이다.

annotation은 속성property를 갖는데, IndexCategorycategory()라는 속성을 갖는다. 이는 Class<?>를 값으로 갖는 속성으로 그 이름은 category가 된다.

위와 같은 IndexCategory annotation을 다음과 같이 사용하고자 할 class에 적용한다.

class Skill extends Thing {}

@IndexCategory(category=Skill.class)
class Magic extends Skill {}

@IndexCategory(category=Skill.class)
class Mastery extends Skill {}

Magic class와 Mastery class는 IndexCategory에 의해 어떤 category에 따라 index를 발급받을지 code 상에 명시한다. 분명 이 annotation은 프로그램의 동작에 영향을 주지만, 어떠한 코드로써 그 정보를 제공한다기 보다는 metadata로 그 정보를 제공한다는 점에서 충분히 매력적이다. 어떠한 class의 특성을 명시하기 위해 method나 field, 상속 등의 무거운 방법을 사용하지 않고, 위처럼 annotation을 통해 충분히 정보를 제공할 수 있다.

문법상 annotation을 사용할 때 앞에 @ 을 붙이고 annotation 이름을 쓴다. 그리고 () 안에 속성=값의 형태로 나열하면 되고, 배열의 경우 {}를 통해 묶어주면 된다.

이러한 annotation을 가장 많이 접하는 경우가 Override annotationDeprecated annotation, 그리고 SuppressWarnings annotation이다.
간단히 Override annotation만 설명하자면 이는 method에 선언 가능하며 source 시간까지 유지되는 annotation이다. 즉 compiler가 확인하고 버리는 시점까지 유지되는 annotation으로 override 되지 않은 method가 이 annotation을 가지고 있을 경우 compile error를 띄워주는 역할을 하여 compile time에 잘못된 override를 사전에 보고하는 역할을 한다.

위와 같이 IndexCategory annotation을 명시한 뒤, 객체를 생성하여 index를 발급하는 코드에서는 이를 반영하여 index를 해주면 된다.

public static int generateNextIndex() {
    String callerClassName = findCallerClassName();
    try {
        Class<?> callerClass = Class.forName(callerClassName);
        if (callerClass.isAnnotationPresent(IndexCategory.class)) {
            IndexCategory category = callerClass.getAnnotation(IndexCategory.class);
            Class<?> categoryClass = category.category();
            return generateNextIndex(categoryClass);
        }
        return generateNextIndex(callerClass);
    } catch (Exception e) {
    }
    return generateNextIndex(callerClassName);
}

callerClassName을 가져와서 그로부터 Class 정보를 가져온 뒤, 그 Class가 IndexCategory annotation이 존재present할 경우 해당 annotation에 명시된 category로부터 category class 정보를 가져와서 그 class로 index를 발급하겠다는 코드이다.

즉, 반드시 IndexCategory를 통해 index grouping을 수행하겠다는 것이 아니라 IndexCategory annotation이 present된 경우에만 해당 class로 index를 발급받는 것이므로, annotation이 선언되지 않은 경우까지 완벽히 처리하게 됩니다.

물론 callerClassName에서 잘못된 class 이름을 얻을 경우에 대비해서 예외처리 코드를 추가했지만 이는 Class#forName 함수가 반드시 예외를 처리하게 하는(명시적 예외처리) 구조이므로 try-catch 문을 작성한 것이지 위 경우에서는 예외가 발생할 가능성이 없다.

본 문서에서는 Java Annotation의 문법에 대해 설명하는 것이 취지가 아니므로, 이 정도로 글을 마친다.

Loading script...