본문 바로가기

프로그래밍/Spring & MyBatis

custom annotation 만들기

1. Annotation

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

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

2. Built-in Annotation

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

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

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

3. Meta Annotations

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

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

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

4. 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; }

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

5. 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".     } }

간단한 annotation 만들기

갑자기 annotation 을 사용해 보고 싶어서 작성을 한 번 해보도록 하자. annotation 은 생각보다 간단하게 만들 수 있다. 일단 만드는 순서는 아래와 같다.


일단 interface 를 하나 만들자.

public interface TestAnno {      String value(); }  

여기에 interface 앞에 @를 붙이자

public @interface TestAnno {      String value(); }  


그러면 annotation 은 다 만든 것이다. 이것은 이제 annotation 처럼 사용하면 된다.

annotation 에서 선언한 method 이름은 아래처럼 annotation을 사용할 때 변수처럼 사용된다. 위의 경우처럼 method 가 한 개인 annotation 에서는 method 이름을 생략해서 사용할 수 있다.

public class Test {          @TestAnno(value="string")     public void doing(){      } } 

 

public class Test {          @TestAnno("string")     public void doing(){      } } 



default value

default 값도 할당할 수 있다.

public @interface TestAnno {      String value() default "myWorld"; } 


default 값이 할당 되어 있으면, 아래처럼 인자를 넣지 않아도 사용할 수 있다. 

public class Test {         @TestAnno     public void doing(){      } }  

 

annotation 의 사용

 ref. 2 에 따르면 annotation 은 metadata 를 표시하기 위한 장치라고 이야기 한다. 이제 까지 필자가 겪어본 annotation 의 사용들을 보면 meta-programming 에 도움을 주는 요소라고 표현할 수 있을 듯 하다.

annotation 을 우리말로 번역하면 "주석" 이다. 즉 소스코드에 부가적인 설명을 덧붙이는 것이다.

이런 설명(annotation)은 그냥 코드의 가독성을 위해 적어놓기도 하고, 또는 다른 code 에서 읽을 수 있기 때문에 programming 으로 처리할 수 있다. 즉 자동화가 가능하다. 여러가지 반복적으로 처리돼야 하는 귀찮은 일들을 source code 안으로 가져와서 자동으로 처리하게 할 수 있다.

이와 관련해서는 annotation 의 Retention 설정을 RUNTIME 으로 해줘야 한다.[ref. 4] 만약 Retention 설정이 RUNTIME 으로 되어 있지 않으면, runtime 에 isAnnotationPresent() 같은 함수에서는 false 를 return 한다. Retention 과 관련해서는 ref. 1 을 참고하도록 하자.


@Retention(RetentionPolicy.RUNTIME) public @interface TestAnno {      String value(); } 

 

public class Main {    public static void main(String[] args) {      Method methods[] = new Test().getClass().getDeclaredMethods();      for (int i = 0; i < methods.length; i++) {         String methodsName = methods[i].getName();         System.out.println(methodsName);         if (methods[i].isAnnotationPresent(TestAnno.class)                 && methodsName.startsWith("do")) {             methods[i].getDeclaredAnnotations();         }      }   } } 



그 밖에

 이 밖에도 annotation 과 관련되어서 아래와 같은 내용들을 ref. 1 에서 읽을 수 있다.

  1. Default 값을  가지는 어노테이션 유형
  2. Target지정하기
  3. Retention 설정 (*retentions 의 사전적 의미는 : 보존, 보유, 감금, 보존력 )
  4. Documented
  5. Inherited
 
 

References

  1. 머찐아빠 :: 나만의 어노테이션(커스텀 어노테이션) 만들기
  2. Bridging the Gap: J2SE 5.0 Annotations, Kyle Downey 10/06/2004
    번역 : annotation 이란?
  3. java - get annotation value? - Stack Overflow
  4. Is it possible to read the value of a annotation in java? - Stack Overflow

 

 

출처 : http://jdm.kr/blog/216