标签: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源文件生成对应的接口文件,仅对类中的公共方法抽象成接口中的方法。
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(),表名生成接口的后缀名。
//老师类 @GenerateInterface (suffix= "IntSuffix" ) public class Teacher { //教书 private void teach(){ System.out.println( "teach..." ); } //行走 public void walk(){ System.out.println( "walking" ); } } |
类Teacher标注上了注解@GenerateInterface,指定生成接口的后缀名为”IntSuffix”。按照预期,生成的接口的名称应为TeacherIntSuffix。
public class Doctor { //诊断 private void diagnose(){ System.out.println( "diagnose..." ); } //行走 public void walk(){ System.out.println( "walking" ); } } |
类Doctor未使用注解,注解处理器将不会为该类生成对应的接口文件。
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来处理它们。
AbstractProcessor主要实现了Processor接口的主要方法:
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 ; } |
public Set<String> getSupportedOptions() { SupportedOptions so = this .getClass().getAnnotation(SupportedOptions. class ); if (so == null ) return Collections.emptySet(); else return arrayToSet(so.value()); } |
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()); } |
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; } |
完全类名:javax.annotation.processing.Filer,注解处理器可用此创建新文件(源文件、类文件、辅助资源文件)。由此方法创建的源文件和类文件将由管理它们的工具(javac)处理。
完全类名:javax.annotation.processing.Messager,注解处理器用此来报告错误消息、警告和其他通知的方式。可以为它的方法传递元素、注解、注解值,以提供消息的位置提示,不过,这类位置提示可能是不可用的,或者只是一个大概的提示。打印错误种类的日志将会产生一个错误。
注意:打印消息可能会出现在System.out、System.out中,也可能不是。也可以选择在窗口中显示消息。
编写真正的注解处理程序CreateInterfaceProcessor,为了演示用,尽量保持处理逻辑的简单性,在此处忽略方法的返回类型和参数的判断,以下具体逻辑:
具体代码实现如下:
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 ; } } |
注解处理器编写完成后,需要使用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.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)
Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)
标签:collect 抽象 ever lan key 集成 new 返回 walk
原文地址:http://www.cnblogs.com/haoerlv/p/7562486.html