1.12. Annotation

어노테이션(Annotation)


1. Annotation 개요


  • annotation은 java 5(1.5) 이상부터 지원되는 기능으로 메타데이터 기능을 지원
  • 코드에 관한 데이터를 제공하며, 효용성으로는 문서화, 컴파일러 체크, 코드분석에서 사용
  • 데코레이션, 클래스, 인터페이스, 필드에 적용되어 툴과 라이브러리를 활용할 수 있게 함으로써, 코드에 명시적 프로그래밍을 줄이고 좀 더 많은 선언문을 제공
  • annotation은 프로그램의 의미적인 부분에 직접 영향을 주지 않고, 툴 내지는 프로세서 혹은 스레드가 이 프로그램을 어떻게 다루어야 하는지에 알려준다. 툴이 실행 중인 프로그램의 의미적인 부분에 영향을 줄 수 있다.
  • 직번역하면 '주석'이지만 annotation은 '주석' 보다는 보다 적극적인 의미를 가진다. 즉, annotation은 일종의 신호를 담은 깃발과 같은 역할을 한다. 특정 기능이 이 깃발을 만나면 깃발에 담긴 신호와 메타 정보에 따라 특정한 처리를 하도록 돕는 역할을 한다. 물론 모든 annotation이 그런 것은 아니다.
  • annotation은 런타임에 소스 파일 또는 클래스 파일 등에서 읽을 수 있다.
  • annotation은 javadoc 태그의 기능을 보안하고 있다. 마크업을 문서 생성시 필요한 정보를 제공하기 위해 사용하고자 할 경우, javadoc 태크 또는 annotation을 사용해야 한다.
  • Annotation의 기능
(1) 컴파일러가 에러를 탐지하거나 경고등을 무시하는 등등의 용도(2) 컴파일 또는 설치시에 소프트웨어 도구가 코드, XML 파일 등을 처리하기 위한 용도
(3) 실행시 별도의 처리 루틴이 필요한 경우(실행 루틴 점검 등과 같은)

2. Annotation의 종류


  • Marker Annotation
- 이름으로 구분하기 위하여 사용하며 추가적인 데이터를 필요하지 않음.

- 멤버 변수가 없으며, 단순히 표식으로서 사용된다. 컴파일러에게 어떤 의미를 전달한다.

Example:
	 public @interface MyAnnotation {
	 }
 Usage:
        @MyAnnotation 
	public void mymethod() { 
			.... 
	} 

  • Single-Value Annotation
- 간단한 신텍스를 사용하며 단일 데이터를 필요로 함

- 멤버로 단일변수만을 갖는다. 단일변수 밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있다.

Example: 
	public @interface MyAnnotation { 
		String doSomething();
	 } 
Usage:
	@MyAnnotation ("What to do")
	 public void mymethod() { 
		....
	 }

  • Full Annotation
- 복잡한 신텍스이며, 다중 데이터를 사용하며 name=value 형태를 취함 --> 데이터가 Array 인 경우 "{ }"를 이용

- 멤버로 둘 이상의 변수를 갖는 어노테이션으로, 데이터를 (값=쌍)의 형태로 전달한다.

Example: 
 public @interface MyAnnotation { 
	 String doSomething();
	 int count;
	 String date();
  } 
Usage: 
	@MyAnnotation (doSomething="What to do", count=1, date="09-09-2005")
	 public void mymethod() { 
		....
	 }

3. 기본 Annotation


  • @Deprecated : 더 이상 사용하지 말아야할 메소드를 알림 (비추천 메소드)
  • @Override : 상위 요소를 오버라이드 할 것임을 알림
  • @SuppressWarnings : 경고를 하지 않도록 억제 시킴
- unchecked : 비확인 - deprecate : 비사용

4. Built-in Annotation


  • @Target : Annotation의 대상을 무엇으로 할 것인지를 기록 (CONSTRUCTOR, FIELD, METHOD, PACKAGE, ...)
- ANNOTATION_TYPE : annotation 타입(Annotation Type)선언 - CONSTRUCTOR : 생성자 함수 선언
- FIELD : 필드 선언(enum 상수 포함)
- LOCAL_VARIABLE : 로컬 변수 선언
- METHOD : 메소드 선언
- PACKAGE : package 선언
- PARAMETER : parameter 선언
- TYPE : 클래스, 인터페이스 (annotation타입(AnnotationType)포함), enum선언

  • @Retention : 어느 과정에서 Annotation을 사용할 것인지를 기록 (여러개의 값 중 선택 가능) (SOURCE, CLASS, RUNTIME)
- SOURCE : 소스파일에서만 사용하며 컴파일 이후는 사용하지 않음 - CLASS : 컴파일 과정까지 사용하며 Runtime에서는 사용하지 않음
- RUNTIME : Runtime에서까지 사용함

  • @Documented : Javadoc에 포함(문서화)되어야 함을 알리는 Marker Annotation
  • @Inherited : 하위 클래스에 상속되어야 함을 알리는 Marker Annotation

5. Custom Annotation

  • 개발자가 정의하는 어노테이션으로 class 형태로 만들어진다.
  • 어노테이션의 선언은 @interface 로 한다.
  • 이름 앞에 '@'문자가 오는 것 외에는 기본적으로 인터페이스를 선언하는 것과 동일(메소드들의 내용은 없고 형태만 선언)하다.
  • default 가 찍히지 않은 메소드는 필수로 입력해야 한다
  • Custom Annotation 적용 사례
- 프로젝트의 유스케이스를 추적하는 어노테이션을 작성- 프로젝트 관리자는 구현된 유스케이스의 수를 이용하여 프로젝트의 진척도를 알 수 있으며 개발자는 시스템 내의 비즈니스 규칙을 변경하거나 디버깅할 때 유스케이스를 쉽게 찾을 수 있으므로 프로젝트의 유지보수를 쉽게 할 수 있도록 지원.

// @UseCase 선언
// UseCase.java
package annotation.usecase;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface UseCase {  
    public int id();  
    public String description() default "no description";  
}  

// 클래스의 메소드에 유스케이스 정보를 기록한 예제
// PasswordUtils.java
package annotation.usecase;  
import java.util.List;    
public class PasswordUtils {  
      
    @UseCase(id = 47, description = "password must contain at least one numeric")  
    public boolean validatePassword(String password){  
        return (password.matches("w*dw*"));  
    }  
      
    @UseCase(id = 48)  
    public String encryptPassword(String password){  
        return new StringBuilder(password).reverse().toString();  
    }  
      
    @UseCase(id = 49, description = "New password can't equal previously used ones")  
    public boolean checkForNewPassword(List<String> prevPasswords, String password){  
        return !prevPasswords.contains(password);  
    }  
} 

// 어노테이션을 적용한 PasswordUtils 클래스를 읽고 리플렉션을 사용하여 
// @UseCase를 검색하는 어노테이션 프로세스 입니다.
// UseCaseTracker.java
package annotation.usecase;  
import java.lang.reflect.Method;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
  
public class UseCaseTracker {  
      
    public static void trackUseCases(List<Integer> useCases, Class<?> cl){  
        for(Method m : cl.getDeclaredMethods()){  
            UseCase uc = m.getAnnotation(UseCase.class);  
            if(uc != null){  
                System.out.println("Found Use Case :" + uc.id() + " " + uc.description());  
                useCases.remove(new Integer(uc.id()));  
            }  
        }  
        for(int i : useCases){  
            System.out.println("Warning : Missing use case-" + i);  
        }  
    }  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        List<Integer> useCases = new ArrayList<Integer>();  
        Collections.addAll(useCases, 47, 48, 49, 50);  
        trackUseCases(useCases, PasswordUtils.class);  
    }  
}  

처리 결과 Found Use Case :47 password must contain at least one numeric
Found Use Case :48 no description
Found Use Case :49 New password cant't equal previously used ones
Warning : Missing use case-50