Java SE/APT demo 01

APT demo 01

Soul-Learner 2009. 12. 9. 10:19

APT(Annotation Processing Tool) 를 사용하여 컴파일 타임(Compile Time)에 Annotation을 검사하는 예

JDK 1.5부터 apt.exe라는 툴이 자바 개발툴로서 포함되어 있다. 이 툴을 이용하면 Compile Time에 Annotation을 조사하여 지시에 따라 보조파일을 생성할 수 있으며, 컴파일 경로를 출력하거나 소스내용을 확인하고 그 소스에 반응하는 작업을 컴파일시에 할 수 있게 된다.

APT를 사용하면 다음과 같은 작업을 수행하게 된다.

1. 지정한 소스파일들이 Annotation을 사용하고 있는지 검사한다.
2. 지정한 소스파일들이 Annotation을 사용하고 있다면, 개발자가 지정한 AnnotationProcessorFactory 를 찾는다.
3. APT는 AnnotationProcessorFactory 에서 AnnotationProcessor객체를 생성하기 위한 기본 데이터를 설정하고 getProcessorFor()를 호출하여 AnnotationProcessor객체를 리턴받는다.
4. APT는 리턴된 AnnotationProcessor객체를 이용하여 process()호출한다.
5. process()에서는 Annotation 의 내용을 검사하여 파일을 생성하거나 경고 메시지를 출력하는 등 개발자가 필요로 하는 작업을 한다(개발자가 오버라이드함)
6. 만약 process()에서 새로 생성한 소스파일이 있고 그 소스파일 안에도 Annotation이 있다면 위와 동일한 절차가 진행된다.
7. 더 이상 새로 생성되는 파일이 없다면 APT는 소스파일들(새로 생성된 소스파일 포함)을 컴파일한다.



반드시 새로운 파일을 생성해야 하는 것은 아니고 보조파일이나 새로운 소스파일을 개발자의 손을 거치지 않고도 편리하게 Annotation을 이용하여 지시하고 컴파일시에 자동으로 생성할 수 있다는 것이다.

주목할 만한 일은 컴파일시에도 개발자가 지정한 로직을 실행할 수 있다는 것인데, 이 때 실행되는 로직을 담을 클래스를 개발자가 작성해야 한다. 컴파일 직전에 Annotation을 검사하고 Annotation에 따라 반응하는 로직을 담을 클래스는 AnnotationProcessor의 process()인데 개발자는 이 클래스를 상속하여 파생된 클래스를 작성하여야 한다. 그리고 AnnotationProcessor의 객체는 AnnotationProcessorFactory 클래스에 의해서 APT에 리턴되기 때문에 개발자는 AnnotationProcessorFactory 클래스를 상속하여 파생된 클래스를 작성해야 한다.

1. AnnotationProcessorFactory : APT에서 이 클래스의 getProcessorFor()를 호출하면 APT에게 AnnotationProcessor객체를 리턴한다.
2. AnnotationProcessor : APT에서 이 클래스의 process()를 호출하여 Annotation처리를 시작한다.

그러므로 APT를 실행하기 위해서는 팩토리 클래스와 프로세서 클래스가 필요하고 Annotation검사의 대상이 되는 소스파일들이 필요하며 다음과 같은 명령으로 APT를 실행할 수 있다.

apt -cp [팩토리 및 프로세서 클래스가 있는 경로 혹은 jar파일 경로] -factory [팩토리 클래스의 완전한 경로(패키지포함경로)] [컴파일할 소스파일들]<enter>


예) factory.jar안의 annotest.factory.MyFactory클래스가 팩토리 클래스이고 동일 jar파일안에 프로세서 클래스가 저장되어 있다고 할 때는 다음과 같은 명령으로 APT를 실행할 수 있다.

apt   -cp     d:\factory\factory.jar    -factory annotest.factory.MyFactory    *.java<enter>


AnnotationProcessorFactory , AnnotationProcessor 등을 사용하기 위해서는 tools.jar에 저장된 라이브러리를 사용하기 때문에 시스템의 CLASSPATH환경변수에 tools.jar파일의 경로를 등록해 주어야 한다.

.;C:\Java\jdk1.6.0_14\lib\tools.jar


테스트에 사용된 소스파일들 (팩토리와 프로세서 클래스는 컴파일하여 jar파일로 압축하는 것이 편리한 사용법이다)

NoteAnnotationProcessorFactory.java

import java.util.*;
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
 
public class NoteAnnotationProcessorFactory implements AnnotationProcessorFactory {
 
 /**
  * Returns a note annotation processor.
  *
  * @return An annotation processor for "Note" annotations if requested,
  * otherwise, returns the NO_OP annotation processor.
  */

 @Override
 public AnnotationProcessor getProcessorFor(
   Set<AnnotationTypeDeclaration> declarations,
   AnnotationProcessorEnvironment env) {
  AnnotationProcessor result;
  if(declarations.isEmpty()) {
   result = AnnotationProcessors.NO_OP;
  }
  else {
   // Next Step - implement this class:
   result = new NoteAnnotationProcessor(env);
  }
  return result;
 
 }
 
 /**
  * This factory only builds processors for the "Note" annotation.
  * @return a collection containing only the "Note" annotation name.
  */

 @Override
 public Collection<String> supportedAnnotationTypes() {
  return Collections.singletonList("Note");
 }
 
 /**
  * No options are supported by this annotation processor.
  * @return an empty list.
  */

 @Override
 public Collection<String> supportedOptions() {
  return Collections.emptyList();
 }
}



NoteAnnotationProcessor.java

import java.util.*;
 
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.util.*;
 
public class NoteAnnotationProcessor implements AnnotationProcessor {
 
 private AnnotationProcessorEnvironment environment;
 
 private AnnotationTypeDeclaration noteDeclaration;
 
 public NoteAnnotationProcessor(AnnotationProcessorEnvironment env) {
  environment = env;
  // get the annotation type declaration for our 'Note' annotation.
  // Note, this is also passed in to our annotation factory - this
  // is just an alternate way to do it.

  noteDeclaration = (AnnotationTypeDeclaration) environment.getTypeDeclaration("Note");
 }

 @Override
 public void process() {
 
  // Get all declarations that use the note annotation.
  Collection<Declaration> declarations = environment.getDeclarationsAnnotatedWith(noteDeclaration);
  for (Declaration declaration : declarations) {
   processNoteAnnotations(declaration);
   System.out.println("-----------------------------------------------------");
  }
 }
 
 private void processNoteAnnotations(Declaration declaration) {
  // Get all of the annotation usage for this declaration.
  // the annotation mirror is a reflection of what is in the source.

  Collection<AnnotationMirror> annotations = declaration.getAnnotationMirrors();
  System.out.println("Annotation 수:"+annotations.size());
  // iterate over the mirrors.
  for (AnnotationMirror mirror : annotations) {
   // if the mirror in this iteration is for our note declaration...
   if(mirror.getAnnotationType().getDeclaration().equals(noteDeclaration)) {
   
    // print out the goodies.
    SourcePosition position = mirror.getPosition();
    Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror.getElementValues();
 
    System.out.println("Declaration: " + declaration.toString());
    System.out.println("Position: " + position);
    System.out.println("Values:");
    for (Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : values.entrySet()) {
     AnnotationTypeElementDeclaration elemDecl = entry.getKey();
     AnnotationValue value = entry.getValue();
     System.out.println("    " + elemDecl + "=" + value);
    }
   }
  }
 }
}


위에 제시된 팩토리, 프로세서 클래스는 컴파일하여 jar파일로 압축하여 사용하는 것이 편리할 것이다. 즉, 위의 2개 클래스는 아래의 소스파일을 컴파일할 때 실행될 로직을 가진 파일이므로 아래의 소스를 컴파일하기 전에 실행될 준비가 되어 있어야 한다.

Note.java (Note Annotation의 선언)

import java.lang.annotation.*;
 
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE, ElementType.FIELD})
public @interface Note {
    String value();
    String priority() default "MEDIUM";
}




MyClass.java (위에 선언된 Note Annotation을 사용하는 클래스)

@Note(value = "This class isn't finished", priority = "HIGH")
public class MyClass {
 
 @Note("This field isn't finished")
 private String fieldA;
 
 @Note("Constructor isn't finished")
 public MyClass() {
 
 }
 
 @Note("methodA isn't finished")
 public void methodA() {
 
 }
}