Tùy biến trình biên dịch java sử lý Annotation của bạn (Annotation Processing Tool)
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu được viết dựa trên:
  • Eclipse 4.4 (LUNA)

  • Java 7

2- Annotation Processing Tool (APT) là gì

Một tình huống đặt ra:

Bạn tạo một vài Annotation của mình và sử dụng chúng trong ứng dụng Java của bạn. Các Annotation đó có một quy tắc sử dụng do bạn đề ra. Bạn muốn trình biên dịch Java thông báo lỗi sử dụng sai quy tắc nếu có trong thời điểm biên dịch. Và nếu bạn sử dụng Eclipse để viết code bạn muốn Eclipse thông báo lỗi sử dụng ngay trên IDE.

Việc đó hoàn toàn khả thi với APT ( Annotation Processing Tool).
Định nghĩa APT:

APT (Java annotation processing tool) là một công cụ mà bạn có thể sử dụng để sử lý annotation trên code nguồn Java. Tất cả cái bạn cần là thi hành (implements) một bộ sử lý Annotation.
 
Ví dụ:

@PublicFinal là một annotation của bạn, quy tắc của bạn là nó chỉ được phép chú thích lên trên method hoặc field có modifier là public và final. Nếu sử dụng sai, thông báo sẽ hiển thị tại thời điểm biên dịch, đồng thời có thông báo trên IDE:

3- Mô hình ví dụ

Đây là mô hình ví dụ tôi sẽ giới thiệu trong tài liệu này:
Các Annotation của bạn:
  • @PublicFinal chỉ sử dụng cho method hoặc field mà có modifier là public và final.
  • @Controller chỉ sử dụng chú thích cho class, và tên class phải có hậu tố Controller.
  • @Action chỉ sử dụng chú thích cho method trả về kiểu String.

Các bộ sử lý PublicFinalProcessor, ControllerProcessor, ActionProcesser sẽ làm nhiệm vụ thông báo nếu sử dụng sai tại thời điểm biên dịch, bao gồm cả hiển thị lỗi thông báo trên IDE Eclipse.
 

4- Project APTProcessor

Trước hết tạo một Project để bắt đầu.
  • APTProcessor
  • Action.java
package org.o7planning.ann;

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.SOURCE)
public @interface Action {

}
  • Controller.java
package org.o7planning.ann;

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.SOURCE)
public @interface Controller {

}
  • PublicFinal.java
package org.o7planning.ann;

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.SOURCE)
public @interface PublicFinal {

}
  • AcctionProccessor.java
package org.o7planning.aptprocessor;

import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;

// Có tác dụng với @Action
@SupportedAnnotationTypes({ "org.o7planning.ann.Action" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ActionProcessor extends AbstractProcessor {

   private Filer filer;
   private Messager messager;

   @Override
   public void init(ProcessingEnvironment env) {
       filer = env.getFiler();
       messager = env.getMessager();
   }

   // annotations - là các Annotation chịu tác dụng của Processor này.
   @Override
   public boolean process(Set<? extends TypeElement> annotations,
           RoundEnvironment env) {
       DevLog.log("\n\n");
       DevLog.log(" ======================================================== ");
       DevLog.log("#process(...) in " + this.getClass().getSimpleName());
       DevLog.log(" ======================================================== ");

       for (TypeElement ann : annotations) {
           DevLog.log(" ==> TypeElement ann = " + ann);
           // Class chua annotation.
           List<? extends Element> es = ann.getEnclosedElements();
           DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
           for (Element e : es) {
               DevLog.log(" ========> EnclosedElement: " + e);
           }
           Element enclosingElement = ann.getEnclosingElement();

           DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);

           ElementKind kind = ann.getKind();
           DevLog.log(" ====> ann.getKind() = " + kind);
           Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);

           DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = "
                   + e2s.size());
           for (Element e2 : e2s) {
               DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
               DevLog.log("           - Kind : " + e2.getKind());

               // @Action chỉ dùng cho method!
               // Thông báo nếu sử dụng sai.
               if (e2.getKind() != ElementKind.METHOD) {
                   DevLog.log("           - Error!!!");
                   messager.printMessage(Kind.ERROR, "@Action using for method only ",
                           e2);
               } else {
                   // Tên method sử dụng @Action.
                   String methodName = e2.getSimpleName().toString();

                   // Biết chắc e2 mô tả method.
                   // (ExecutableElement mô tả cho method, constructor,..)
                   // (ExecutableElement described for method, constructor,...)
                   ExecutableElement method = (ExecutableElement) e2;

                   DevLog.log("           - method : " + method);
                   TypeMirror retType = method.getReturnType();
                   DevLog.log("           -- method.getReturnType() : "
                           + retType);

                   // @Action chỉ sử dụng cho method trả về String
                   // Thông báo nếu sử dụng sai.
                   if (!String.class.getName().equals(retType.toString())) {
                       DevLog.log("           - Error!!!");
                       messager.printMessage(Kind.ERROR,
                               "Method using @Action must return String", e2);
                   }
               }
           }
       }
       return true;
   }

}
  • ControllProcessor.java
package org.o7planning.aptprocessor;

import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;

// Có tác dụng với @Controller

@SupportedAnnotationTypes({ "org.o7planning.ann.Controller" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ControllerProcessor extends AbstractProcessor {

   private Filer filer;
   private Messager messager;

   @Override
   public void init(ProcessingEnvironment env) {
       filer = env.getFiler();
       messager = env.getMessager();
   }

   // annotations - là các Annotation chịu tác dụng của Processor này.
   @Override
   public boolean process(Set<? extends TypeElement> annotations,
           RoundEnvironment env) {
       DevLog.log("\n\n");
       DevLog.log(" ======================================================== ");
       DevLog.log("#process(...) in " + this.getClass().getSimpleName());
       DevLog.log(" ======================================================== ");

       for (TypeElement ann : annotations) {
           DevLog.log(" ==> TypeElement ann = " + ann);
           //
           List<? extends Element> es = ann.getEnclosedElements();
           DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
           for (Element e : es) {
               DevLog.log(" ========> EnclosedElement: " + e);
           }
           Element enclosingElement = ann.getEnclosingElement();

           DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);

           ElementKind kind = ann.getKind();
           DevLog.log(" ====> ann.getKind() = " + kind);
           Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);

           DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = "
                   + e2s.size());
           for (Element e2 : e2s) {
               DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
               DevLog.log("           - Kind : " + e2.getKind());

               // @Controller chỉ dùng cho class!
               // Thông báo nếu sử dụng sai.
               if (e2.getKind() != ElementKind.CLASS) {
                   DevLog.log("           - Error!!!");
                   messager.printMessage(Kind.ERROR,
                           "@Controller using for class only ", e2);
               } else {
                   // Tên class sử dụng @Controller

                   String className = e2.getSimpleName().toString();
                   // @Controller chỉ áp dụng cho class có đuôi là Controller
                   // Thông báo nếu sử dụng sai.

                   if (!className.endsWith("Controller")) {
                       DevLog.log("           - Error!!!");
                       messager.printMessage(
                               Kind.ERROR,
                               "Class using @Controller must have suffix Controller",
                               e2);
                   }
               }
           }

       }


       return true;
   }

}
  • PublicFinalProcessor.java
package org.o7planning.aptprocessor;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;

// Có tác dụng với @PublicFinal
@SupportedAnnotationTypes(value = { "org.o7planning.ann.PublicFinal" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class PublicFinalProcessor extends AbstractProcessor {

   private Filer filer;
   private Messager messager;

   @Override
   public void init(ProcessingEnvironment env) {
       filer = env.getFiler();
       messager = env.getMessager();
   }

   @Override
   public boolean process(Set<? extends TypeElement> annotations,
           RoundEnvironment env) {
       DevLog.log("\n\n");
       DevLog.log(" ======================================================== ");
       DevLog.log("#process(...) in " + this.getClass().getSimpleName());
       DevLog.log(" ======================================================== ");

       // annotations ở đây mô tả các annotation
       // thuộc phạm vi sử lý của Processor này.
       // Vì Processor này được định nghĩa chỉ dùng cho @PublicFinal
       // cho nên chắc chắn annotations chỉ có 1 phần tử.
       DevLog.log(" annotations count = " + annotations.size());

       // TypeElement mô tả các annotation
       // thuộc phạm vi Processor này sử lý.
       for (TypeElement ann : annotations) {
           
           // Các phần tử được chú thích bởi Annotation @PublicFinal
           // Element ở đây mô tả một đối tượng được @PublicFinal chú thích
           Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
           for (Element e2 : e2s) {
               DevLog.log("- e2 = " + e2);

               Set<Modifier> modifiers = e2.getModifiers();

               // @PublicFinal chỉ áp dụng cho public & final
               // Thông báo nếu sử dụng sai.
               if (!(modifiers.contains(Modifier.FINAL) && modifiers
                       .contains(Modifier.PUBLIC))) {
                   DevLog.log("- Error!!!");
                   messager.printMessage(Kind.ERROR,
                           "Method/field wasn't public and final", e2);

               }
           }
       }

       // Tất cả đã được sử lý bởi Processor này.
       return true;
   }
}
  • DevLog.java
package org.o7planning.log;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class DevLog {

  public static final String LOG_FILE = "C:/APT/log.txt";

  public static void log(Object message) {
      if (message == null) {
          return;
      }
     
      // Make sure the path exists.
      new File(LOG_FILE).getParentFile().mkdirs();
      //
      FileWriter writer = null;
      try {
          writer = new FileWriter(LOG_FILE, true);
          writer.append(message.toString());
          writer.append("\n");
          writer.close();
      } catch (IOException e) {
          e.printStackTrace();
          try {
              writer.close();
          } catch (IOException e1) {
          }
      }
  }
}

Khai báo Service

Tạo file javax.annotation.processing.Processor nằm trong thư mục META-INF/services như hình minh họa dưới đây:
  • javax.annotation.processing.Processor
org.o7planning.aptprocessor.PublicFinalProcessor
org.o7planning.aptprocessor.ActionProcessor
org.o7planning.aptprocessor.ControllerProcessor

Đóng gói project APTProcessor ra file jar:

Nhấn phải vào Project chọn Export:
Export thành công:

5- Project APTTutorial

Tạo Project APTTutorial:
Nhấn phải chuột vào  APTTutorial chọn property.
Khai báo sử dụng thư viện APTProssor mà bạn vừa tạo ra trước đó.
Khai báo sử dụng Annotation Processor của bạn với Compiler.
Khai báo vị trí thư viện Processor:
Bạn có thể nhấn vào Advanced.. để thấy được các Processor đã được đăng ký với Compiler
Nhấn OK để hoàn thành:
Tạo một số class test sử dụng Annotation và Processor của bạn:
  • PublicFinalTest.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.PublicFinal;

public class PublicFinalTest {

   @PublicFinal
   public final static int ABC = 100;

   @PublicFinal
   private static String MODULE_NAME = "APT";  
   
   
}
Thông báo lỗi nhìn thấy trên IDE:
  • TestActionController_01.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;

@Controller
public class TestActionController_01 {

   @Action
   public String exit() {
       return null;
   }

   @Action
   public void print() {

   }

   @Action
   public int error() {
       return 0;
   }
}
  • TestActionController_02.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Controller;

@Controller
public interface TestActionController_02 {

   public String close();

}
  • TestActionController.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;

@Controller
public class TestActionController {

   @Action
   public String login() {
       return null;
   }
}

6- Xem Log

Bạn có thể xem log mà Processor sử lý tại:
  • C:/APT/log.txt
log.txt do class DevLog tạo ra, chỉ có mục đích khi lập trình.