编译时注解解析

来源:互联网 发布:刘邦历史书籍 知乎 编辑:程序博客网 时间:2024/06/05 00:13

【参考资料】

注解处理器(编译期|RetentionPolicy.SOURCEhttps://blog.zenfery.cc/archives/78.html

 

当注解的@RetentionCLASS时,此注解为编译时注解。

如果一个元素上面使用了编译时注解,那么我们可以在编译阶段,解析该元素上的编译时注解,生成自己想要的.java源代码文件。

解析编译时注解需要用到注解处理器(APTAnnotation 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接口

...

 

要实现上面的需求,我们的注解处理器可以按照如下逻辑来处理:

循环每一个需要编译处理的类(即TeacherDoctor),找出有注解@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);

          //初始化FilerMessager

          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指定注解处理器


可以看出,注解处理器循环执行了三次。

第一次,对TeacherDoctor类进行处理,并生成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;

     }

输出日志如下


原创粉丝点击