Настройте java compiler для обработки вашего Annotation (Annotation Processing Tool)

View more categories:

1- Введение

Статья основана на:
  • Eclipse 4.6 (NEON)

  • Java 7

2- Что такое Annotation Processing Tool (APT)

Приводится ситуация:

Вы создаете некоторые свои Annotation и используете их в вашем приложении  Java. Эти Annotation имеют правила использования данные вами. Вы хотитте, чтобы компилятор Java (Java compiler) оповещал ошибку использования не по правилам если они возникают во время компиляции. И если вы используете Eclipse чтобы написать код, вы хотите чтобы Eclipse оповещал ошибки использования прямо на  IDE.

Это вполне выполнимо с  APT ( Annotation Processing Tool).
Определение  APT:
APT (Java annotation processing tool) это инструмент, который вы можете использовать для обработки annotation на исходном коде  Java. Все что вам нужно это выполнить (implements) процессор Annotation.

  • Например:
@PublicFinal это ваш annotation, ваше правило - он может аннотировать только на методе или поле с модификатром, являющимся публичным и финальным. Если использовать неправильно, оповещение отобразится во время компиляции, одновременно выдает оповещение на IDE:

3- Модель примера

Это модель примера, который я представлю в этой статье:
Ваши Annotation:
  • @PublicFinal используется только для метода или поля с модификатором (modifier), являющимися публичными и финальными (public và final).
  • @Controller использутеся только для клсса, и название класса должно иметь суффикс Controller.
  • @Action используется тоько для метода, возвращающего вид String.
Процессоры  PublicFinalProcessor, ControllerProcessor, ActionProcesser будут выполнять задание оповещения при неправильном использовании во время компиляции, включая отображение оповещения ошибок на  IDE Eclipse.

4- Project APTProcessor

Для начала, создадим Project.
  • 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;

// Work with @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();
  }


  @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());


              // @Action use for method only
              // notify if misuse
              if (e2.getKind() != ElementKind.METHOD) {
                  DevLog.log("           - Error!!!");
                  messager.printMessage(Kind.ERROR, "@Action using for method only ",
                          e2);
              } else {

                  // The name of the method is annotated by @Action
                  String methodName = e2.getSimpleName().toString();

                  // (ExecutableElement described for method, constructor,...)
                  ExecutableElement method = (ExecutableElement) e2;

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


                  // @Action Only used for method returns the String
                  // Notify if misuse
                  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;


// Apply for @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();
  }


  @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 only use for Class
              // Notify if misuse
              if (e2.getKind() != ElementKind.CLASS) {
                  DevLog.log("           - Error!!!");
                  messager.printMessage(Kind.ERROR,
                          "@Controller using for class only ", e2);
              } else {

                  // The name of the class is annotated by @Controller
                  String className = e2.getSimpleName().toString();

                  // @Controller using for class with suffix Controller
                  // Notify if misuse
                  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;


// Apply for @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(" ======================================================== ");

      DevLog.log(" annotations count = " + annotations.size());

      for (TypeElement ann : annotations) {

          Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
          for (Element e2 : e2s) {
              DevLog.log("- e2 = " + e2);

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

              // @PublicFinal only using for public & final
              // Notify if misuse
              if (!(modifiers.contains(Modifier.FINAL) && modifiers
                      .contains(Modifier.PUBLIC))) {
                  DevLog.log("- Error!!!");
                  messager.printMessage(Kind.ERROR,
                          "Method/field wasn't public and final", e2);

              }
          }
      }

      // All PublicFinal annotations are handled by this Processor.
      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) {
         }
     }
 }
}

Объявить Service

Создать файл  javax.annotation.processing.Processor в папке  META-INF/services как в изображении ниже:
javax.annotation.processing.Processor
org.o7planning.aptprocessor.PublicFinalProcessor
org.o7planning.aptprocessor.ActionProcessor
org.o7planning.aptprocessor.ControllerProcessor

Упаковка project APTProcessor в file jar:

Нажмите на правую кнопку мыши на Project и выберите Export:
Export успешно:

5- Project APTTutorial

Создать Project APTTutorial:
Нажать на правую кнопку мыши на   APTTutorial и выбрать properties.
Объявить использование библиотеки  APTProccessor который вы создали до этого.
Объявить использование вашего  Annotation ProcessorCompiler.
Объявить местоположение библиотеки  Processor:
Вы можете нажать на Advanced.. чтобы увидеть какой Processor уже зарегистрирован с Compiler
Нажмите на OK чтобы завершить:
Создать некоторые классы test используя ваши Annotation и Processor:
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";  
 
 
}
Оповещение ошибки отображенное на 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- Просмотр Log

Вы можете просмотреть log который обработан с помощью Processor здесь:
  • C:/APT/log.txt
log.txt do class DevLog созлан, только с целью использования при программировании.

View more categories: