标签:collect 抽象 ever lan key 集成 new 返回 walk
注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理。在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译。
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源文件生成对应的接口文件,仅对类中的公共方法抽象成接口中的方法。
| importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target(ElementType.TYPE)//注解使用目标为类@Retention(RetentionPolicy.SOURCE)//注解保留范围为源代码public@interfaceGenerateInterface {  String suffix() default"Interface";//生成对应接口的后缀名} | 
定义注解的保留范围为源代码级别,仅包含一个注解元素suffix(),表名生成接口的后缀名。
| //老师类@GenerateInterface(suffix="IntSuffix")publicclassTeacher {  //教书  privatevoidteach(){    System.out.println("teach...");  }  //行走  publicvoidwalk(){    System.out.println("walking"); }} | 
类Teacher标注上了注解@GenerateInterface,指定生成接口的后缀名为”IntSuffix”。按照预期,生成的接口的名称应为TeacherIntSuffix。
| publicclassDoctor {  //诊断  privatevoiddiagnose(){    System.out.println("diagnose...");  }  //行走  publicvoidwalk(){    System.out.println("walking");  }} | 
类Doctor未使用注解,注解处理器将不会为该类生成对应的接口文件。
JDK6中提供的注解处理工具框架的主要类包为javax.annotation.processing。处理器的核心接口为:javax.annotation.processing.Processor,还提供了一个此接口的实现类:javax.annotation.processing.AbstractProcessor。处理接口提供了一个核心处理方法process(),用于开发者实现自己的处理逻辑(用于处理先前round中产生的注解)。
| publicabstractbooleanprocess(Set<? extendsTypeElement> annotations    , RoundEnvironment roundEnv); | 
process()方法有一个boolean类型的返回值,若返回false,表示本轮注解未声明并且可能要求后续其它的Processor处理它们;若返回true,则代表这些注解已经声明并且不要求后续Processor来处理它们。
AbstractProcessor主要实现了Processor接口的主要方法:
| publicsynchronizedvoidinit(ProcessingEnvironment processingEnv) {  if(initialized)    thrownewIllegalStateException("Cannot call init more than once.");  if(processingEnv == null)    thrownewNullPointerException("Tool provided null ProcessingEnvironment");  this.processingEnv = processingEnv;  initialized = true;} | 
| publicSet<String> getSupportedOptions() {  SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);  if(so == null)    returnCollections.emptySet();  else    returnarrayToSet(so.value());} | 
| publicSet<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.");        returnCollections.emptySet();    }    else        returnarrayToSet(sat.value());} | 
| publicSourceVersion 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();  returnsv;} | 
完全类名:javax.annotation.processing.Filer,注解处理器可用此创建新文件(源文件、类文件、辅助资源文件)。由此方法创建的源文件和类文件将由管理它们的工具(javac)处理。
完全类名:javax.annotation.processing.Messager,注解处理器用此来报告错误消息、警告和其他通知的方式。可以为它的方法传递元素、注解、注解值,以提供消息的位置提示,不过,这类位置提示可能是不可用的,或者只是一个大概的提示。打印错误种类的日志将会产生一个错误。
注意:打印消息可能会出现在System.out、System.out中,也可能不是。也可以选择在窗口中显示消息。
编写真正的注解处理程序CreateInterfaceProcessor,为了演示用,尽量保持处理逻辑的简单性,在此处忽略方法的返回类型和参数的判断,以下具体逻辑:
具体代码实现如下:
| importjava.io.IOException;importjava.io.Writer;importjava.util.List;importjava.util.Set;importjavax.annotation.processing.AbstractProcessor;importjavax.annotation.processing.Filer;importjavax.annotation.processing.Messager;importjavax.annotation.processing.ProcessingEnvironment;importjavax.annotation.processing.RoundEnvironment;importjavax.annotation.processing.SupportedAnnotationTypes;importjavax.annotation.processing.SupportedSourceVersion;importjavax.lang.model.SourceVersion;importjavax.lang.model.element.Element;importjavax.lang.model.element.Modifier;importjavax.lang.model.element.TypeElement;importjavax.lang.model.type.ExecutableType;importjavax.tools.Diagnostic.Kind;importjavax.tools.JavaFileObject;importcom.zenfery.example.annotation.GenerateInterface;//生成接口的处理类 ,在此不考虑方法的参数及返回类型(为了演示简单)@SupportedAnnotationTypes("com.zenfery.example.annotation.GenerateInterface")//@SupportedOptions({"name"})@SupportedSourceVersion(SourceVersion.RELEASE_6)publicclassCreateInterfaceProcessor extendsAbstractProcessor{  privateFiler filer;  privateMessager messager;  privateintr = 1;//轮循次数  @Override  publicsynchronizedvoidinit(ProcessingEnvironment processingEnv) {    super.init(processingEnv);    //初始化Filer和Messager    this.filer = processingEnv.getFiler();    this.messager = processingEnv.getMessager();  }  @Override  publicbooleanprocess(Set<? extendsTypeElement> annotations, RoundEnvironment roundEnv) {    messager.printMessage(Kind.NOTE, "process() is execute...");    //获取所有编译类元素,并打印,测试用    Set<? extendsElement> elements = roundEnv.getRootElements();    System.out.println("输入的所有类有:");    for(Element e: elements){      System.out.println(">>> "+e.getSimpleName());    }    //获取使用了注解@GenerateInterface的类元素    System.out.println("需要生成相应接口的类有:");    Set<? extendsElement> 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<? extendsElement> 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() instanceofExecutableType && 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");    returntrue;  }  //判断元素是否为public  publicbooleanisPublic(Element e){    //获取元素的修饰符Modifier,注意此处的Modifier    //非java.lang.reflect.Modifier    Set<Modifier> modifiers = e.getModifiers();    for(Modifier m: modifiers){      if(m.equals(Modifier.PUBLIC)) returntrue;    }    returnfalse;  }} | 
注解处理器编写完成后,需要使用java提供的工具javac来执行才能真正的起作用。下面介绍一下javac工具相关注解的选项。
用法:javac <选项> <源文件>
其中,注解可能乃至选项包括:
-cp <路径> 指定查找用户类文件和注释处理程序的位置。
-proc:{none,only} 控制是否执行注释处理和/或编译。-proc:none表示编译期不执行注解处理器; -proc:only表示只执行注解处理器,不进行任何注解之后的编译。
-processor <class1>[,<class2>,<class3>…]要运行的注释处理程序的名称;绕过默认的搜索进程。
-processorpath <路径> 指定查找注释处理程序的位置。如果未指定,将使用-cp指定的路径。
-d <目录> 指定存放生成的类文件的位置。
-s <目录> 指定存放生成的源文件的位置。
-Akey[=value] 传递给注释处理程序的选项。
命令的执行目录为工程的根目录。执行前的目录结构:
| $ tree src/ classes/src/`-- com    `-- zenfery        `-- example            `-- annotation                |-- bean                |   |-- Doctor.java                |   `-- Teacher.java                |-- GenerateInterface.java                `-- proc                    `-- CreateInterfaceProcessor.javaclasses/ | 
编译注解处理器及注解程序。
命令:$ 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.javaclasses/`-- 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)
Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)
标签:collect 抽象 ever lan key 集成 new 返回 walk
原文地址:http://www.cnblogs.com/haoerlv/p/7562486.html