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中,可供子类使用。实现如下:
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;} - getSupportedOptions()方法:获取通过注解@SupportedOptions设置的可支持的输入选项值(-A参数),具体实现如下:
publicSet<String> getSupportedOptions() {SupportedOptions so =this.getClass().getAnnotation(SupportedOptions.class);if(so ==null)returnCollections.emptySet();elsereturnarrayToSet(so.value());} - getSupportedAnnotationTypes()方法:指定注解处理器可解析的注解类型,结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 “name.*” 形式的名称,表示所有以 “name.” 开头的规范名称的注释类型集合。最后,”*” 自身表示所有注释类型的集合,包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。具体实现如下:
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();}elsereturnarrayToSet(sat.value());} - getSupportedSourceVersion()方法:指定该注解处理器支持的最新的源版本,默认为版本6。具体实现如下:
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 +".");}elsesv = ssv.value();returnsv;}
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对象生成源码类。
具体代码实现如下:
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.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)的更多相关文章
- Java编译期注解处理器详细使用方法
目录 Java编译期注解处理器 启用注解处理器 遍历语法树 语法树中的源节点 语法树节点的操作 给类增加注解 给类增加import语句 构建一个内部类 使用方法 chainDots方法 总结 Java ...
- 简单介绍 Java 中的注解 (Annotation)
1. 例子 首先来看一个例子: @Override public String toString() { return "xxxxx"; } 这里用了 @Override, 目的是 ...
- java中的注解总结
1. 什么是注解 注解是java5引入的特性,在代码中插入一种注释化的信息,用于对代码进行说明,可以对包.类.接口.字段.方法参数.局部变量等进行注解.注解也叫元数据(meta data).这些注解信 ...
- 【转载】JAVA基础:注解
原文:https://www.cnblogs.com/xdp-gacl/p/3622275.html#undefined 一.认识注解 注解(Annotation)很重要,未来的开发模式都是基于注解的 ...
- java @Retention元注解
@Retention元注解 有三种取值:RetentionPolicy.SOURCE.RetentionPolicy.CLASS.RetentionPolicy.RUNTIME分别对应:Java源文件 ...
- Java 内置注解简单理解
感谢原文作者:yejg1212 原文链接 https://www.cnblogs.com/yejg1212/p/3187362.html https://www.cnblogs.com/yejg121 ...
- 【转】java注解处理器——在编译期修改语法树
https://blog.csdn.net/a_zhenzhen/article/details/86065063 前言从需求说起由于相关政策,最近公司安全部要求各系统在rpc接口调用的交互过程中把相 ...
- Java注解(2)-注解处理器(运行时|RetentionPolicy.RUNTIME)
如果没有用来读取注解的工具,那注解将基本没有任何作用,它也不会比注释更有用.读取注解的工具叫作注解处理器.Java提供了两种方式来处理注解:第一种是利用运行时反射机制:另一种是使用Java提供的API ...
- Java注解处理器--编译时处理的注解
1. 一些基本概念 在开始之前,我们需要声明一件重要的事情是:我们不是在讨论在运行时通过反射机制运行处理的注解,而是在讨论在编译时处理的注解.注解处理器是 javac 自带的一个工具,用来在编译时期扫 ...
随机推荐
- Caused by: java.lang.NoClassDefFoundError: org/springframework/web/context/WebApplicationContext
1.错误描述 严重: A child container failed during start java.util.concurrent.ExecutionException: org.apache ...
- freemarker自定义标签报错(一)
freemarker自定义标签 1.错误描述 freemarker.core.ParseException: Token manager error: freemarker.core.TokenMgr ...
- 解析Java中的String、StringBuilder、StringBuffer类(一)
引言 String 类及其相关的StringBuilder.StringBuffer 类在 Java 中的使用相当的多,在各个公司的面试中也是必不可少的.因此,在本周,我打算花费一些时间来认真的研读一 ...
- webpack学习(二):先写几个webpack基础demo
一.先写一个简单demo1 1-1安装好webpack后创建这样一个目录: 1-2:向src各文件和dist/index.html文件写入内容: <!DOCTYPE html> <h ...
- CentOS 7.x 防火墙开放端口相关用法记录
前言 防火墙对服务器起到一定的保护作用,所以了解一些相关的操作是很有必要的. 在CentOS 7.x中,有了一种新的防火墙策略,FireWall , 还记得在6.x中用的还是iptables. 这几天 ...
- 【BZOJ3172】单词(AC自动机)
[BZOJ3172]单词(AC自动机) 题面 Description 某人读论文,一篇论文是由许多单词组成.但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次. Input ...
- [CQOI2007]余数求和
大于k的部分直接加k 对于小于等于k的cnt个数 ans=cnt*k - Σ(k/i * i) 然后k/i在一段区间内不变,这段区间直接可以数列求和 # include <bits/stdc++ ...
- SpringMVC 注解式开发
SpringMVC的注解式开发是指,处理器是基于注解的类的开发.对于每一个定义的处理器,无需再配置文件中逐个注册,只需在代码中通过对类与方法的注解,便可完成注册.即注解替换是配置文件中对于处理器的注册 ...
- 八年架构师大咖首次揭秘,年薪50W秘籍!
序言 我是土生土长的老北京人,你们肯定觉得我很有钱,为啥呢? 因为觉得我是北京户口,其实你们错了,我的房子是靠我自己买的,父母基本上没帮到我什么,当然,我也不需要吧! 我只想说,作为一名程序员,我很自 ...
- Chrome 浏览器各版本下载大全【转载】
随着最近64位版本的 Chrome 浏览器正式版的推出,Chrome 浏览器再次受到广大浏览迷的重点关注,今天我们就整理一下各版本的 Chrome 浏览器 32位及64位的下载地址,方便各位浏览迷选择 ...