betacode

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

View more Tutorials:

Следуйте за нами на нашей фан-странице, чтобы получать уведомления каждый раз, когда появляются новые статьи. Facebook

1- Что такое 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:

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

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

3- 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 успешно:

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

5- Просмотр Log

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

View more Tutorials:

Maybe you are interested

Это онлайн курс вне вебсайта o7planning, который мы представляем, он включает бесплатные курсы или курсы со скидкой.