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;\n"

        +"public interface "+className+" {\n";

      //获取所有的方法元素

      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()+"();\n";

        }

      }

      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++)+"次循环处理结束...\n");

    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)

时间: 2024-10-20 09:45:23

Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)的相关文章

Java Reflection(八):注解

转载自并发编程网 – ifeve.com 内容索引什么是注解类注解方法注解参数注解变量注解 利用Java反射机制可以在运行期获取Java类的注解信息. 什么是注解 注解是Java 5的一个新特性.注解是插入你代码中的一种注释或者说是一种元数据(meta data).这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理.下面是一个类注解的例子: @MyAnnotation(name="someName", va

Java编译期和运行期

Q.下面的代码片段中,行A和行B所标识的代码有什么区别呢? ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ConstantFolding {       static final  int number1 = 5;       static final  int number2 = 6;       static int number3 = 5;       static int number4= 6;       

java @Retention元注解

@Retention元注解 有三种取值:RetentionPolicy.SOURCE.RetentionPolicy.CLASS.RetentionPolicy.RUNTIME分别对应:Java源文件(.java文件)---->.class文件---->内存中的字节码 Retention注解说明 当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时可能会把Java源程序上的一些注解给去掉,java编译器

Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)

如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用.读取注解的工具叫作注解处理器.Java提供了两种方式来处理注解:第一种是利用运行时反射机制:另一种是使用Java提供的API来处理编译期的注解. 反射机制方式的注解处理器 仅当定义的注解的@Retention为RUNTIME时,才能够通过运行时的反射机制来处理注解.下面结合例子来说明这种方式的处理方法. Java中的反射API(如java.lang.Class.java.lang.reflect.Field等)都实现了接

java--自定义注解(注解在编译时生效)

转:https://www.cnblogs.com/yaoxiaowen/p/6753964.html 若注解在运行时(Runtime)通过反射机制来处理注解,既然是Runtime,那么总会有效率上的损耗,如果我们能够在编译期(Compile time)就能处理注解,那自然更好,而很多框架其实都是在编译期处理注解,比如大名鼎鼎的bufferknife,这个过程并不复杂,只需要我们自定义注解处理器(Annotation Processor)就可以了.(Annotation Processor下文有

Java注解(1)-注解基础

注解(Annotation)是在JAVA5中开始引入的,它为在代码中添加信息提供了一种新的方式.注解在一定程度上把元数据与源代码文件结合在一起,正如许多成熟的框架(Spring)所做的那样.那么,注解到底可以做什么呢? 1.注解的作用. 提供用来完整地描述程序所需要的信息,如编译期校验程序信息. 生成描述符文件,或生成新类的定义. 减轻编写"样板"代码(配置文件)的负担,可以使用注解自动生成. 更加干净易读的代码. 编译期类型检查. 2.Java提供的注解 Java5内置了一些原生的注

Java高级之注解、反射

Java的注解.反射等机制的产生,让动态代理成为可能,一般通过全限定名+类名,找到类,可以invoke它的构造方法以及其他方法,可以获取它的参数(Field)名称和值. 注解一般用在代码的注释上.代码审查上(有没有按标准写,比如inspect).代码注入(hook,asbectj),需要考虑的是,在何时注入(编译期还运行期) 反射一般用在动态将json和Object互相转化,执行相关底层代码,比如设置某个类的Accessible为false,防止别人hook修改 例:阿里的FastJson解析:

Java编译期优化与运行期优化技术浅析

Java语言的“编译期”是一段不确定的过程,因为它可能指的是前端编译器把java文件转变成class字节码文件的过程,也可能指的是虚拟机后端运行期间编译器(JIT)把字节码转变成机器码的过程. 下面讨论的编译期优化指的是javac编译器将java文件转化为字节码的过程,而运行期间优化指的是JIT编译器所做的优化. 编译期优化 虚拟机设计团队把对性能的优化集中到了后端的即时编译器(JIT)中,这样可以让那些不是由javac编译器产生的class文件也同样能享受到编译器优化所带来的好处.但是java

Effective Java - 枚举与注解

Enumeration 于Java 1.5增加的enum type... enum type是由一组固定的常量组成的类型,比如四个季节.扑克花色. 在出现enum type之前,通常用一组int常量表示枚举类型. 比如这样: public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public