注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理。在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译。
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开发包的类库中是不存在的)。在JDK6中,将注解处理器这一功能进行了规范化,形成了java.annotation.processing的API包,Mirror API则进行封装,形成javax.lang.model包。注解处理器的开发进行了简化,不再单独使用apt工具,而将此功能集成到了javac命令中。(当前开发使用的JDK版本一般都在6以上,故对apt工具不做研究)。
编译期注解处理器
通过一个示例程序来解释编译期注解处理器的使用(javac工具来处理)。
使用注解处理器将给定的java源文件生成对应的接口文件,仅对类中的公共方法抽象成接口中的方法。
2.1、定义注解@GenerateInterface
package com.zenfery.example.annotation;
|
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:
package com.zenfery.example.annotation;
|
@GenerateInterface (suffix= "IntSuffix" )
|
System.out.println( "teach..." );
|
System.out.println( "walking" );
|
类Teacher标注上了注解@GenerateInterface,指定生成接口的后缀名为”IntSuffix”。按照预期,生成的接口的名称应为TeacherIntSuffix。
2.2.2、编写测试类Doctor
package com.zenfery.example.annotation;
|
System.out.println( "diagnose..." );
|
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) {
|
throw new IllegalStateException( "Cannot call init more than once." );
|
if (processingEnv == null )
|
throw new NullPointerException( "Tool provided null ProcessingEnvironment" );
|
this .processingEnv = processingEnv;
|
- getSupportedOptions()方法:获取通过注解@SupportedOptions设置的可支持的输入选项值(-A参数),具体实现如下:
public Set<String> getSupportedAnnotationTypes() {
|
SupportedAnnotationTypes sat = this .getClass()
|
.getAnnotation(SupportedAnnotationTypes. class );
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
|
"No SupportedAnnotationTypes annotation " +
|
"found on " + this .getClass().getName() +
|
", returning an empty set." );
|
return Collections.emptySet();
|
return arrayToSet(sat.value());
|
- getSupportedAnnotationTypes()方法:指定注解处理器可解析的注解类型,结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 “name.*” 形式的名称,表示所有以 “name.” 开头的规范名称的注释类型集合。最后,”*” 自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。具体实现如下:
public Set<String> getSupportedAnnotationTypes() {
|
SupportedAnnotationTypes sat = this .getClass()
|
.getAnnotation(SupportedAnnotationTypes. class );
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
|
"No SupportedAnnotationTypes annotation " +
|
"found on " + this .getClass().getName() +
|
", returning an empty set." );
|
return Collections.emptySet();
|
return arrayToSet(sat.value());
|
- getSupportedSourceVersion()方法:指定该注解处理器支持的最新的源版本,默认为版本6。具体实现如下:
public SourceVersion getSupportedSourceVersion() {
|
SupportedSourceVersion ssv = this .getClass()
|
.getAnnotation(SupportedSourceVersion. class );
|
sv = SourceVersion.RELEASE_6;
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
|
"No SupportedSourceVersion annotation " +
|
"found on " + this .getClass().getName() +
|
", returning " + 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,为了演示用,尽量保持处理逻辑的简单性,在此处忽略方法的返回类型和参数的判断,以下具体逻辑:
- 循环每一个需要编译处理的类(即Teacher、Doctor),找出有注解@GenerateInterface标识的类(即Teacher)。
- 找到Teacher类中所有的public方法。
- 根据类名和方法名,使用Filer对象生成源码类。
具体代码实现如下:
package com.zenfery.example.annotation.proc;
|
import java.io.IOException;
|
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" )
|
@SupportedSourceVersion (SourceVersion.RELEASE_6)
|
public class CreateInterfaceProcessor extends AbstractProcessor{
|
private Messager messager;
|
public synchronized void init(ProcessingEnvironment processingEnv) {
|
super .init(processingEnv);
|
this .filer = processingEnv.getFiler();
|
this .messager = processingEnv.getMessager();
|
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());
|
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();
|
"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" ;
|
JavaFileObject jfo = filer
|
.createSourceFile( "com.zenfery.example.annotation.bean." +className, e);
|
Writer writer = jfo.openWriter();
|
writer.append(classString);
|
} catch (IOException ex) {
|
System.out.println( "-------------------注解处理器第" +(r++)+ "次循环处理结束...\n" );
|
public boolean isPublic(Element e){
|
Set<Modifier> modifiers = e.getModifiers();
|
for (Modifier m: modifiers){
|
if (m.equals(Modifier.PUBLIC)) return true ;
|
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、执行编译
命令的执行目录为工程的根目录。执行前的目录结构:
|-- GenerateInterface.java
|
`-- CreateInterfaceProcessor.java
|
编译注解处理器及注解程序。
命令:$ javac -d classes/ src/com/zenfery/example/annotation/proc/*.java src/com/zenfery/example/annotation/*.java
执行命令,生成GenerateInterface.class、CreateInterfaceProcessor.class,此时的目录结构如下:
|-- GenerateInterface.java
|
`-- CreateInterfaceProcessor.java
|
|-- GenerateInterface.class
|
`-- 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... |
>>>> 类Teacher封装元素(仅对修饰符有public的生成接口方法): |
>>> >>> <init> 修饰符:[public] |
>>> >>> teach 修饰符:[private] |
>>> >>> walk 修饰符:[public] |
-------------------注解处理器第1次循环处理结束... |
注意:process() is execute... |
-------------------注解处理器第2次循环处理结束... |
注意:process() is execute... |
-------------------注解处理器第3次循环处理结束... |
可以看出,注解处理器循环执行了三次。第一次,对Teacher和Doctor类进行处理,并生成Teacher类对应的接口类TeacherIntSuffix;第二次,对第一次生成的类TeacherIntSuffix再做处理,这一次将不再产生新的类。第三次,未能发现新生成的类,执行结束。
此时目录结构如下:
| `-- TeacherIntSuffix.java
|
|-- GenerateInterface.java
|
`-- CreateInterfaceProcessor.java
|
| `-- TeacherIntSuffix.class
|
|-- GenerateInterface.class
|
`-- CreateInterfaceProcessor.class
|
生成了TeacherIntSuffix.java类,并进行了编译生成了TeacherIntSuffix.class。TeacherIntSuffix.java类如下:
package com.zenfery.example.annotation.bean;
|
public interface TeacherIntSuffix {
|
后记:本节内容,在日常应用中使用的概率非常小,仅供理解。
转载请注明:子暃之路 » Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)
相关推荐
- `@Retention(RetentionPolicy.SOURCE)`:注解只存在于源码阶段,编译后不保留。 - `@Retention(RetentionPolicy.CLASS)`:注解在编译后存在于字节码中,但JVM运行时不考虑。 - `@Retention(RetentionPolicy....
保留策略定义了注解在何时何地可以被读取,常见的有`RetentionPolicy.SOURCE`(源码阶段)、`RetentionPolicy.CLASS`(编译阶段)和`RetentionPolicy.RUNTIME`(运行时)。 在实际开发中,注解广泛应用于框架和库中...
1. **源码级注解(Source-level Annotations)**:这些注解在编译期间由编译器处理,例如`@Override`用于标记重写父类方法,`@Deprecated`用于标记已废弃的方法。 2. **编译器注解(Compiler annotations)**:这些...
- `RetentionPolicy.SOURCE`表示注解仅存在于源代码中,编译时丢弃。 7. **元注解** - 元注解是用来修饰其他注解的注解,如`@Retention`, `@Target`, `@Documented` 和 `@Inherited`。 - `@Target`指定注解可...
- `@Retention(RetentionPolicy.SOURCE)`:注解只存在于源代码,编译后不再存在。 5. **注解的使用位置** - `@Target`可以指定注解应用于TYPE(类、接口、枚举)、FIELD(字段)、METHOD(方法)、PARAMETER...
* RetentionPolicy.SOURCE:注解只保留在源代码中,编译时被忽略。 * RetentionPolicy.CLASS:注解被编译到字节码中,但运行时被忽略(默认值)。 * RetentionPolicy.RUNTIME:注解在运行时也保留,并可以被反射机制...
- `RetentionPolicy.SOURCE`:注解只存在于源代码中,编译后将被丢弃,不会出现在字节码文件中。 - `RetentionPolicy.CLASS`:注解在编译后的字节码文件中保留,但运行时无法通过反射访问。 - `RetentionPolicy....
- RetentionPolicy.SOURCE - 注解只在源代码级别存在,编译后不会保留。 - RetentionPolicy.CLASS - 注解在类文件中可见,但JVM在运行时不保留。 - RetentionPolicy.RUNTIME - 注解在运行时仍然可用,可以通过...
* `RetentionPolicy.SOURCE`:指定注解只保留在一个源文件当中。 * `RetentionPolicy.CLASS`:指定注解只保留在一个 class 文件中。 * `RetentionPolicy.RUNTIME`:指定注解可以保留在程序运行期间。 2. `@Target...
`@Retention`元注解用于指定注解的生命周期,例如`RetentionPolicy.SOURCE`表示注解只存在于源码阶段,`RetentionPolicy.CLASS`表示注解会在字节码中保留,但运行时无法访问,而`RetentionPolicy.RUNTIME`则意味着...
注解处理器是Java编译器的一部分,可以在编译期间检测到特定注解的存在,并根据这些注解生成额外的源代码或资源。例如,`@Generated`注解常用于标记由注解处理器生成的代码。 ```java @Generated("MyProcessor") ...
Java 注解详解 Java 注解(Annotation)是 JDK 5.0 及以后版本引入的一个特性,它是一个新的类型,与类、接口、枚举是在同一个层次。注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些...
`RetentionPolicy.SOURCE`表示注解仅存在于源代码阶段,编译后不再保留;`RetentionPolicy.CLASS`意味着注解在类文件中保留,但JVM加载时不考虑;`RetentionPolicy.RUNTIME`则表明注解在运行时仍可被JVM读取,通常...
在Android中,我们通常使用Java的`@Retention(RetentionPolicy.CLASS)`或`@Retention(RetentionPolicy.SOURCE)`策略来定义编译时注解。 1. **注解处理器**:在仿照ButterKnife的Demo中,关键在于创建一个自定义的...
- RetentionPolicy.SOURCE:注解只存在于源代码级别,编译后不会保留。 - RetentionPolicy.CLASS:注解在编译后会存在于字节码文件中,但JVM加载时不保留。 - RetentionPolicy.RUNTIME:注解会在运行时保留,可以...
在Java中,我们可以使用`@Retention(RetentionPolicy.SOURCE)`或`@Retention(RetentionPolicy.CLASS)`声明编译时注解。 接下来,我们来看如何实现`@BindView`和`@ContentView`这两个注解。`@BindView`用于将XML布局...
`RetentionPolicy`定义了注解的生命周期,它可以是在源码阶段(SOURCE)、编译时(CLASS)或运行时(RUNTIME)。如果选择RUNTIME,那么注解信息可以在运行时通过反射获取。 3. **目标类型**(Target) `Target`...
1. **编译时处理**:通过`@Retention(RetentionPolicy.CLASS)`或`@Retention(RetentionPolicy.SOURCE)`,注解可以在编译时被处理。编译器可以检查注解并采取相应行动,例如,检查`@Override`是否真的重写了父类方法...
`RetentionPolicy.SOURCE` 表示注解只存在于源代码级别,在编译后就不再保留;`RetentionPolicy.CLASS` 表示注解在编译后的字节码中保留,但在运行时无法访问;而 `RetentionPolicy.RUNTIME` 表示注解会在运行时依然...
- `RetentionPolicy.SOURCE`:注解仅存在于源代码中,在编译阶段会被丢弃。 - `RetentionPolicy.CLASS`:注解将被编译器保留到.class文件中,但虚拟机在加载类时会忽略它。 - `RetentionPolicy.RUNTIME`:注解将...