zoukankan      html  css  js  c++  java
  • Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

    注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理。在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译。

    Java5中提供了apt工具来进行编译期的注解处理。apt是命令行工具,与之配套的是一套描述“程序在编译时刻的静态结构”的API:Mirror API(com.sun.mirror.*)。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑。具体的处理工具交给apt来处理。编写注解处理器的核心是两个类:注解处理器(com.sun.mirror.apt.AnnotationProcessor)、注解处理器工厂(com.sun.mirror.apt.AnnotationProcessorFactory)。apt工具在完成注解处理后,会自动调用javac来编译处理完成后的源代码。然而,apt工具是oracle提供的私有实现(在JDK开发包的类库中是不存在的)。在 Java8中,已经移除了 APT 工具;在JDK6中,将注解处理器这一功能进行了规范化,形成了java.annotation.processing的API包,Mirror API则进行封装,形成javax.lang.model包。注解处理器的开发进行了简化,不再单独使用apt工具,而将此功能集成到了javac命令中。(当前开发使用的JDK版本一般都在6以上,故对apt工具不做研究)。

    编译期注解处理器

    通过一个示例程序来解释编译期注解处理器的使用(javac工具来处理)。

    使用注解处理器将给定的java源文件生成对应的接口文件,仅对类中的公共方法抽象成接口中的方法。

    2.1、定义注解@GenerateInterface

    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 GenerateInterface {
      String suffix() default "Interface";//生成对应接口的后缀名
    }

    定义注解的保留范围为源代码级别,仅包含一个注解元素suffix(),表名生成接口的后缀名。

    2.2、编写测试Java类

    2.2.1、编写测试类Teacher:

    //老师类
    @GenerateInterface(suffix="IntSuffix")
    public class Teacher {
     
      //教书
      private void teach(){
        System.out.println("teach...");
      }
     
      //行走
      public void walk(){
        System.out.println("walking");
     }
    }

    类Teacher标注上了注解@GenerateInterface,指定生成接口的后缀名为”IntSuffix”。按照预期,生成的接口的名称应为TeacherIntSuffix。

    2.2.2、编写测试类Doctor

    public class Doctor {
     
      //诊断
      private void diagnose(){
        System.out.println("diagnose...");
      }
     
      //行走
      public void walk(){
        System.out.println("walking");
      }
    }

    类Doctor未使用注解,注解处理器将不会为该类生成对应的接口文件。

    2.3、编写注解处理器

    JDK6中提供的注解处理工具框架的主要类包为javax.annotation.processing。处理器的核心接口为:javax.annotation.processing.Processor,还提供了一个此接口的实现类:javax.annotation.processing.AbstractProcessor。处理接口提供了一个核心处理方法process(),用于开发者实现自己的处理逻辑(用于处理先前round中产生的注解)。

    public abstract boolean process(Set<? extends TypeElement> annotations
        , RoundEnvironment roundEnv);

    process()方法有一个boolean类型的返回值,若返回false,表示本轮注解未声明并且可能要求后续其它的Processor处理它们;若返回true,则代表这些注解已经声明并且不要求后续Processor来处理它们。

    2.3.1、AbstractProcessor虚拟类

    AbstractProcessor主要实现了Processor接口的主要方法:

    • init(ProcessingEnvironment processingEnv)方法:使用处理环境类初始化Processor类,将ProcessingEnvironment环境存入成员变量processingEnv中,可供子类使用。实现如下:
      public synchronized void init(ProcessingEnvironment processingEnv) {
        if (initialized)
          throw new IllegalStateException("Cannot call init more than once.");
        if (processingEnv == null)
          throw new NullPointerException("Tool provided null ProcessingEnvironment");
       
        this.processingEnv = processingEnv;
        initialized = true;
      }
    • getSupportedOptions()方法:获取通过注解@SupportedOptions设置的可支持的输入选项值(-A参数),具体实现如下:
      public Set<String> getSupportedOptions() {
        SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
        if (so == null)
          return Collections.emptySet();
        else
          return arrayToSet(so.value());
      }
    • getSupportedAnnotationTypes()方法:指定注解处理器可解析的注解类型,结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 “name.*” 形式的名称,表示所有以 “name.” 开头的规范名称的注释类型集合。最后,”*” 自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。具体实现如下:
      public Set<String> getSupportedAnnotationTypes() {
          SupportedAnnotationTypes sat = this.getClass()
              .getAnnotation(SupportedAnnotationTypes.class);
          if (sat == null) {
              if (isInitialized())
                  processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                      "No SupportedAnnotationTypes annotation " "found on "
                      this.getClass().getName() +
                      ", returning an empty set.");
              return Collections.emptySet();
          }
          else
              return arrayToSet(sat.value());
      }
    • getSupportedSourceVersion()方法:指定该注解处理器支持的最新的源版本,默认为版本6。具体实现如下:
      public SourceVersion getSupportedSourceVersion() {
        SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
        SourceVersion sv = null;
        if (ssv == null) {
          sv = SourceVersion.RELEASE_6;
          if (isInitialized())
              processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                       "No SupportedSourceVersion annotation " +
                                                       "found on " this.getClass().getName() +
                                                       ", returning " + sv + ".");
        else
          sv = ssv.value();
        return sv;
      }

    2.3.2、Filer接口

    完全类名:javax.annotation.processing.Filer,注解处理器可用此创建新文件(源文件、类文件、辅助资源文件)。由此方法创建的源文件和类文件将由管理它们的工具(javac)处理。

    2.3.3、Messager接口

    完全类名:javax.annotation.processing.Messager,注解处理器用此来报告错误消息、警告和其他通知的方式。可以为它的方法传递元素、注解、注解值,以提供消息的位置提示,不过,这类位置提示可能是不可用的,或者只是一个大概的提示。打印错误种类的日志将会产生一个错误。

    注意:打印消息可能会出现在System.out、System.out中,也可能不是。也可以选择在窗口中显示消息。

    2.3.4、自定义注解处理类CreateInterfaceProcessor

    编写真正的注解处理程序CreateInterfaceProcessor,为了演示用,尽量保持处理逻辑的简单性,在此处忽略方法的返回类型和参数的判断,以下具体逻辑:

    1. 循环每一个需要编译处理的类(即Teacher、Doctor),找出有注解@GenerateInterface标识的类(即Teacher)。
    2. 找到Teacher类中所有的public方法。
    3. 根据类名和方法名,使用Filer对象生成源码类。

    具体代码实现如下:

    import java.io.IOException;
    import java.io.Writer;
    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.Modifier;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.type.ExecutableType;
    import javax.tools.Diagnostic.Kind;
    import javax.tools.JavaFileObject;
     
    import com.zenfery.example.annotation.GenerateInterface;
     
    //生成接口的处理类 ,在此不考虑方法的参数及返回类型(为了演示简单)
    @SupportedAnnotationTypes("com.zenfery.example.annotation.GenerateInterface")
    //@SupportedOptions({"name"})
    @SupportedSourceVersion(SourceVersion.RELEASE_6)
    public class CreateInterfaceProcessor extends AbstractProcessor{
     
      private Filer filer;
      private Messager messager;
     
      private int r = 1;//轮循次数
     
      @Override
      public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //初始化Filer和Messager
        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();
      }
     
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Kind.NOTE, "process() is execute...");
        //获取所有编译类元素,并打印,测试用
        Set<? extends Element> elements = roundEnv.getRootElements();
        System.out.println("输入的所有类有:");
        for(Element e: elements){
          System.out.println(">>> "+e.getSimpleName());
        }
     
        //获取使用了注解@GenerateInterface的类元素
        System.out.println("需要生成相应接口的类有:");
        Set<? extends Element> genElements = roundEnv.getElementsAnnotatedWith(GenerateInterface.class);
        for(Element e: genElements){
          System.out.println(">>> "+e.getSimpleName());
          GenerateInterface gi = e.getAnnotation(GenerateInterface.class);
          String className = e.getSimpleName()+gi.suffix();
          String classString = "package com.zenfery.example.annotation.bean; "
            +"public interface "+className+" { ";
          //获取所有的方法元素
          List<? extends Element> genElementAlls = e.getEnclosedElements();
          System.out.println(">>>> 类"+e.getSimpleName()+"封装元素(仅对修饰符有public的生成接口方法):");
          for(Element e1 : genElementAlls){
            System.out.println(">>> >>> "+e1.getSimpleName()+" 修饰符:"+e1.getModifiers());
            if(!e1.getSimpleName().toString().equals("<init>") && e1.asType() instanceof ExecutableType && isPublic(e1)){
              System.out.println(">>> >>> >>> "+e1.getSimpleName());
              classString += " void "+e1.getSimpleName()+"(); ";
            }
          }
          classString+="}";
          //System.out.println(classString);
          try {
            JavaFileObject jfo = filer.createSourceFile("com.zenfery.example.annotation.bean."+className, e);
            Writer writer = jfo.openWriter();
            writer.flush();
            writer.append(classString);
            writer.flush();
            writer.close();
          catch (IOException ex) {
            ex.printStackTrace();
          }
        }
        System.out.println("-------------------注解处理器第"+(r++)+"次循环处理结束... ");
        return true;
      }
     
      //判断元素是否为public
      public boolean isPublic(Element e){
        //获取元素的修饰符Modifier,注意此处的Modifier
        //非java.lang.reflect.Modifier
        Set<Modifier> modifiers = e.getModifiers();
        for(Modifier m: modifiers){
          if(m.equals(Modifier.PUBLIC)) return true;
        }
        return false;
      }
     
    }

    2.4、执行注解处理器

    注解处理器编写完成后,需要使用java提供的工具javac来执行才能真正的起作用。下面介绍一下javac工具相关注解的选项。

    2.4.1、javac对注解处理支持

    用法:javac <选项> <源文件>

    其中,注解可能乃至选项包括:

    -cp <路径> 指定查找用户类文件和注释处理程序的位置。

    -proc:{none,only} 控制是否执行注释处理和/或编译。-proc:none表示编译期不执行注解处理器; -proc:only表示只执行注解处理器,不进行任何注解之后的编译。

    -processor <class1>[,<class2>,<class3>…]要运行的注释处理程序的名称;绕过默认的搜索进程。

    -processorpath <路径>        指定查找注释处理程序的位置。如果未指定,将使用-cp指定的路径。

    -d <目录> 指定存放生成的类文件的位置。

    -s <目录> 指定存放生成的源文件的位置。

    -Akey[=value] 传递给注释处理程序的选项。

    2.4.2、执行编译

    命令的执行目录为工程的根目录。执行前的目录结构:

    $ tree src/ classes/
    src/
    `-- com
        `-- zenfery
            `-- example
                `-- annotation
                    |-- bean
                    |   |-- Doctor.java
                    |   `-- Teacher.java
                    |-- GenerateInterface.java
                    `-- proc
                        `-- CreateInterfaceProcessor.java
    classes/

    编译注解处理器及注解程序。

    命令:$ javac -d classes/ src/com/zenfery/example/annotation/proc/*.java src/com/zenfery/example/annotation/*.java

    执行命令,生成GenerateInterface.class、CreateInterfaceProcessor.class,此时的目录结构如下:

    $ tree src/ classes/
     src/
     `-- com
         `-- zenfery
             `-- example
                 `-- annotation
                     |-- bean
                     |   |-- Doctor.java
                     |   `-- Teacher.java
                     |-- GenerateInterface.java
                     `-- proc
                         `-- CreateInterfaceProcessor.java
     classes/
     `-- com
         `-- zenfery
             `-- example
                 `-- annotation
                     |-- GenerateInterface.class
                     `-- proc
                         `-- CreateInterfaceProcessor.class

    执行注解处理器。命令:$ javac -cp classes/ -processor com.zenfery.example.annotation.proc.CreateInterfaceProcessor -d classes/ -s src/ src/com/zenfery/example/annotation/bean/*.java

    标准输出日志:

    注意:process() is execute...
    输入的所有类有:
    >>> Doctor
    >>> Teacher
    需要生成相应接口的类有:
    >>> Teacher
    >>> 类Teacher封装元素(仅对修饰符有public的生成接口方法):
    >>> >>> >>> 修饰符:[public]
    >>> >>> teach 修饰符:[private]
     >>> >>> walk 修饰符:[public]
     >>> >>> >>> walk
    -------------------注解处理器第1次循环处理结束...
     
    注意:process() is execute...
    输入的所有类有:
    >>> TeacherIntSuffix
    需要生成相应接口的类有:
    -------------------注解处理器第2次循环处理结束...
     
    注意:process() is execute...
    输入的所有类有:
     需要生成相应接口的类有:
    -------------------注解处理器第3次循环处理结束...

    可以看出,注解处理器循环执行了三次。第一次,对Teacher和Doctor类进行处理,并生成Teacher类对应的接口类TeacherIntSuffix;第二次,对第一次生成的类TeacherIntSuffix再做处理,这一次将不再产生新的类。第三次,未能发现新生成的类,执行结束。

    此时目录结构如下:

    $ tree src/ classes/
    src/
    `-- com
        `-- zenfery
            `-- example
                `-- annotation
                    |-- bean
                    |   |-- Doctor.java
                    |   |-- Teacher.java
                    |   `-- TeacherIntSuffix.java
                    |-- GenerateInterface.java
                    `-- proc
                        `-- CreateInterfaceProcessor.java
    classes/
    `-- com
        `-- zenfery
            `-- example
                `-- annotation
                    |-- bean
                    |   |-- Doctor.class
                    |   |-- Teacher.class
                    |   `-- TeacherIntSuffix.class
                    |-- GenerateInterface.class
                    `-- proc
                        `-- CreateInterfaceProcessor.class

    生成了TeacherIntSuffix.java类,并进行了编译生成了TeacherIntSuffix.class。TeacherIntSuffix.java类如下:

    package com.zenfery.example.annotation.bean;
    public interface TeacherIntSuffix {
      void walk();
    }

    后记:本节内容,在日常应用中使用的概率非常小,仅供理解。

    转载请注明:子暃之路 » Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

  • 相关阅读:
    tornado web 框架的认识
    JavaScript 作用域知识点梳理
    服务器
    git——学习
    webservice——和API接口
    celery——任务调度模块
    supervisor——进程管理工具
    Python常用的语句
    数据类型比较总结
    字符集和字符编码问题
  • 原文地址:https://www.cnblogs.com/haoerlv/p/7562486.html
Copyright © 2011-2022 走看看