编译时注解解析
来源:互联网 发布:刘邦历史书籍 知乎 编辑:程序博客网 时间:2024/06/05 00:13
【参考资料】
注解处理器(编译期|RetentionPolicy.SOURCE)https://blog.zenfery.cc/archives/78.html
当注解的@Retention为CLASS时,此注解为编译时注解。
如果一个元素上面使用了编译时注解,那么我们可以在编译阶段,解析该元素上的编译时注解,生成自己想要的.java源代码文件。
解析编译时注解需要用到注解处理器(APT,Annotation Processing Tool)。
以如下两个类为例
@CreateInterface("IntSuffix")
publicclass Teacher {
//教书
privatevoidteach(){
System.out.println("teach...");
}
//行走
publicvoid walk(){
System.out.println("walking");
}
}
publicclass Doctor {
//诊断
privatevoiddiagnose(){
System.out.println("diagnose...");
}
//行走
publicvoid walk(){
System.out.println("walking");
}
}
对于每个类,我们想抽取其中的public方法生成一个接口,这个接口的名称为原类名+CreateInterface注解的value参数值。(所以实际上只有Teacher类会生成相应的接口)
@Target(ElementType.TYPE)//注解使用目标为类
@Retention(RetentionPolicy.CLASS)//编译时注解
public@interfaceCreateInterface {
String value() default"Interface";//生成对应接口的后缀
}
编写
那么我们该如何编写注解处理器呢?
编写注解处理器所使用的包为javax.annotation.processing,主要用到
AbstractProcessor抽象类
此类实现了Processor接口的init()、getSupportOption()、getSupportSourceVersion()等方法,帮助我们完成了环境初始化工作。我们写自定义注解处理器,主要就是继承此类,并重写他的process()方法。
Filer接口
用于创建新文件(.java源文件、.class字节码文件、辅助资源文件)
Messager接口
...
要实现上面的需求,我们的注解处理器可以按照如下逻辑来处理:
循环每一个需要编译处理的类(即Teacher、Doctor),找出有注解@GenerateInterface标识的类(即Teacher)。
找到Teacher类中所有的public方法。
根据类名和方法名,使用Filer对象生成源码类。
具体实现代码如下
//注解处理器
@SupportedAnnotationTypes("com.zenfery.example.anno.CreateInterface")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
publicclass CreateInterfaceProcessorextends AbstractProcessor {
private Filerfiler;
// privateMessager messager;
privateintr = 1;//循环次数
@Override
publicsynchronizedvoid init(ProcessingEnvironmentprocessingEnv) {
super.init(processingEnv);
//初始化Filer和Messager
this.filer = processingEnv.getFiler();
// this.messager = processingEnv.getMessager();
}
@Override
publicboolean process(Set<?extends TypeElement>annotations, RoundEnvironmentroundEnv) {
System.out.println("-------------------注解处理器第" + (r++) +"次处理开始-------------------");
//获取所有输入的元素//应该是一个.java文件是一个元素
Set<? extends Element> elements =roundEnv.getRootElements();
System.out.println("输入的所有元素有:");
for (Elemente :elements) {
System.out.println(">>> " +e.getSimpleName());
}
//获取使用了注解@GenerateInterface的元素//这里就是Teacher类//类是TypeElement
Set<? extends Element> genElements =roundEnv.getElementsAnnotatedWith(CreateInterface.class);
System.out.println("使用了注解@GenerateInterface的元素有:");
//对于每一个元素
for (Elemente :genElements) {
System.out.println(">>> " +e.getSimpleName());
//获取该元素上的注解
CreateInterfacegi =e.getAnnotation(CreateInterface.class);
//获取注解的参数值//生成的带后缀的接口名
String className=e.getSimpleName() + gi.value();
//用于拼接源代码的字符串
String classString ="package com.zenfery.example.bean;\n" + "public interface " +className +" {\n";
//获取该元素的所有子元素//因为Teacher类中只定义了方法,所以这里就是获取到所有的方法//方法是ExecutableElement
List<? extends Element> genElementAlls =e.getEnclosedElements();
//获取该类的非构造函数的public方法(在此处忽略方法的返回类型和参数的判断)
for (Elemente1 :genElementAlls) {
if (!e1.getSimpleName().toString().equals("<init>") && e1instanceof ExecutableElement && isPublic(e1)) {
classString +=" void " +e1.getSimpleName() +"();\n";
}
}
classString +="}";
//写到.java文件中
try {
JavaFileObject jfo =filer.createSourceFile("com.zenfery.example.bean." + className,e);
Writer writer =jfo.openWriter();
writer.flush();
writer.append(classString);
writer.flush();
writer.close();
} catch (IOExceptionex) {
ex.printStackTrace();
}
}
returntrue;
}
//判断元素的修饰符是否为public
publicboolean isPublic(Elemente) {
//获取元素的修饰符Modifier,注意此处的Modifier非反射中的java.lang.reflect.Modifier,是javax.lang.model.element中的
Set<Modifier> modifiers =e.getModifiers();
for (Modifierm :modifiers) {
if (m.equals(Modifier.PUBLIC))
returntrue;
}
returnfalse;
}
}
使用
代码编写好以后,如何使用注解处理器呢?在Java8之前,有专门的APT工具来使用注解处理器,在Java8以后,APT工具已经集成到javac命令中,所以我们可以直接使用javac来使用注解处理器
处理前目录结构为
1、编译注解处理器源代码,生成注解处理器.class文件
javac -encodingutf-8 -d bin\ src\com\zenfery\example\anno\*.javasrc\com\zenfery\example\anno\proc\*.java
编译注解和注解处理器.java文件,在bin目录下生成.class文件
2、使用注解处理器
javac -encodingutf-8 -d bin\ -cp bin\ -processorcom.zenfery.example.anno.proc.CreateInterfaceProcessor src\com\zenfery\example\anno\*.javasrc\com\zenfery\example\bean\*.java
其中-cp指定注解处理器.class文件的路径,-processor指定注解处理器
可以看出,注解处理器循环执行了三次。
第一次,对Teacher和Doctor类进行处理,并生成Teacher类对应的接口类TeacherIntSuffix;
第二次,对第一次生成的类TeacherIntSuffix再做处理,这一次将不再产生新的类。
第三次,未能发现新生成的类,执行结束。
处理完成以后,会在bin\com\zenfery\example\bean目录下生成TeacherIntSuffix.java文件,如下所示
回到Processor中,在解析使用了注解的源代码时,需要使用到Element类,Element用于代表源代码中的一个元素,可以分为TypeElement(类或接口等)、VariableElement(成员变量)、ExecuteableElement(成员方法),他们之间的层次关系如下所示
可以通过element.getEnclosedElements()获取所有子元素,getEnclosingElement()获取父元素
element.getSimpleName()获取元素的名称,element.asType()获取元素的类型名称
以解析如下源代码为例
@TestAnno("class")
publicclass Test {
@TestAnno("field")
publicintid;
@TestAnno("method")
public String get(){
return"";
}
}
使用如下注解处理器进行解析
@Override
publicboolean process(Set<?extends TypeElement>annotations, RoundEnvironmentroundEnv) {
System.out.println("-------------------注解处理器第" + (r++) +"次处理开始-------------------");
//获取所有输入的元素//应该是一个.java文件是一个元素
Set<? extends Element> elements =roundEnv.getRootElements();
System.out.println("输入的所有元素有:");
for (Elemente :elements) {
System.out.println(">>> " +e.getSimpleName());
}
//获取使用了注解@TestAnno的元素
Set<? extends Element> genElements =roundEnv.getElementsAnnotatedWith(TestAnno.class);
System.out.println("使用了注解@TestAnno的元素有:");
//对于每一个元素
for (Elemente :genElements) {
//判断该元素的类型
if(einstanceof TypeElement)
System.out.println("TypeElement");//可以通过e.getEnclosedElements()获取所有子Element
elseif(einstanceof VariableElement)
System.out.println("VariableElement");//可以通过e.getEnclosingElement()获取父Element
elseif(einstanceof ExecutableElement)
System.out.println("ExecutableElement");
//获取该元素的名称
System.out.println("name = " +e.getSimpleName());
//获取该元素的类型名称
System.out.println("type = "+e.asType());
TestAnnoannotation =e.getAnnotation(TestAnno.class);
System.out.println("anno value = "+annotation.value());
}
returntrue;
}
输出日志如下
- 编译时注解解析
- Android 编译时解析注解
- android studio 编译时注解(一) 工作原理解析
- Java注解全解析(三)——编译时注解示例
- 编译时注解
- Android 编译时注解
- 编译时注解参考文献
- Android 编译时注解
- 编译时注解
- 关于编译时注解
- 运行时注解解析
- 运行时注解解析
- Java注解处理器(编译时注解)
- Android 打造编译时注解解析框架 这只是一个开始
- Android 打造编译时注解解析框架 这只是一个开始
- Android 打造编译时注解解析框架 这只是一个开始
- Android 打造编译时注解解析框架 这只是一个开始
- Android 打造编译时注解解析框架 这只是一个开始
- 时间:UTC时间、GMT时间、本地时间、Unix时间戳
- 金钱格式化
- Dcokerfile构建Centos-ssh镜像
- GITLAB修改分支权限
- Android kotlin入门与基础语法二
- 编译时注解解析
- Node -- 模块机制
- try,catch
- CentOS-解决CentOS 7 history命令不显示操作记录的时间和用户身份问题
- profiles 配置
- java现在还很热!
- Python-unittest测试框架
- kali linux 开机输入密码无法进入界面
- SonarQube代码质量管理平台安装与使用