编译时注解解析

【参考资料】

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

 

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

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

解析编译时注解需要用到注解处理器(APTAnnotation Processing Tool)。

 

以如下两个类为例

@CreateInterface("IntSuffix")

public class Teacher {

 

  //教书

  private void teach(){

    System.out.println("teach...");

  }

 

  //行走

  public void walk(){

    System.out.println("walking");

  }

}

public class Doctor {

 

  //诊断

  private void diagnose(){

    System.out.println("diagnose...");

  }

 

  //行走

  public void walk(){

    System.out.println("walking");

  }

}

 

对于每个类,我们想抽取其中的public方法生成一个接口,这个接口的名称为原类名+ CreateInterface注解的value参数值。(所以实际上只有Teacher类会生成相应的接口)

@Target(ElementType.TYPE) // 注解使用目标为类

@Retention(RetentionPolicy.CLASS) // 编译时注解

public @interface CreateInterface {

       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)

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);

           // 初始化FilerMessager

           this.filer = processingEnv.getFiler();

           // this.messager = processingEnv.getMessager();

      }

 

      @Override

      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

 

           System.out.println("-------------------注解处理器第" + (r++) + "次处理开始-------------------");

 

           // 获取所有输入的元素//应该是一个.java文件是一个元素

           Set<? extends Element> elements = roundEnv.getRootElements();

          

           System.out.println("输入的所有元素有:");

           for (Element e : elements) {

                 System.out.println(">>> " + e.getSimpleName());

           }

 

           // 获取使用了注解@GenerateInterface的元素//这里就是Teacher//类是TypeElement

           Set<? extends Element> genElements = roundEnv.getElementsAnnotatedWith(CreateInterface.class);

           System.out.println("使用了注解@GenerateInterface的元素有:");

 

           //对于每一个元素

           for (Element e : genElements) {

                

                 System.out.println(">>> " + e.getSimpleName());

                

                 //获取该元素上的注解

                 CreateInterface gi = 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 (Element e1 : genElementAlls) {

                     

                      if (!e1.getSimpleName().toString().equals("<init>") && e1 instanceof 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 (IOException ex) {

                      ex.printStackTrace();

                 }

           }

 

           return true;

      }

 

      // 判断元素的修饰符是否为public

      public boolean isPublic(Element e) {

           // 获取元素的修饰符Modifier,注意此处的Modifier非反射中的java.lang.reflect.Modifier,是javax.lang.model.element中的

           Set<Modifier> modifiers = e.getModifiers();

           for (Modifier m : modifiers) {

                 if (m.equals(Modifier.PUBLIC))

                      return true;

           }

           return false;

      }

 

}

 

使用

代码编写好以后,如何使用注解处理器呢?在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")

public class Test {

 

      @TestAnno("field")

      public int id;

     

      @TestAnno("method")

      public String get(){

           return "";

      }

}

使用如下注解处理器进行解析

      @Override

      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

 

           System.out.println("-------------------注解处理器第" + (r++) + "次处理开始-------------------");

          

           // 获取所有输入的元素//应该是一个.java文件是一个元素

           Set<? extends Element> elements = roundEnv.getRootElements();

          

           System.out.println("输入的所有元素有:");

           for (Element e : elements) {

                 System.out.println(">>> " + e.getSimpleName());

           }

 

           // 获取使用了注解@TestAnno的元素

           Set<? extends Element> genElements = roundEnv.getElementsAnnotatedWith(TestAnno.class);

           System.out.println("使用了注解@TestAnno的元素有:");

 

           //对于每一个元素

           for (Element e : genElements) {

                

                 //判断该元素的类型

                 if(e instanceof TypeElement)

                      System.out.println("TypeElement");//可以通过e.getEnclosedElements()获取所有子Element

                 else if(e instanceof VariableElement)

                      System.out.println("VariableElement");//可以通过e.getEnclosingElement()获取父Element

                 else if(e instanceof ExecutableElement)

                       System.out.println("ExecutableElement");

                

                 //获取该元素的名称

                 System.out.println("name = " + e.getSimpleName());

                 //获取该元素的类型名称

                 System.out.println("type = "+e.asType());

                

                 TestAnno annotation = e.getAnnotation(TestAnno.class);

                 System.out.println("anno value = "+annotation.value());

                

           }

 

           return true;

      }

输出日志如下


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值