动机

近期在看ButterKnife源代码的时候。竟然发现有一个类叫做AbstractProcessor,并且ButterKnife的View绑定不是依靠反射来实现的,而是使用了编译时的注解,自己主动生成的.class文件。

真是一个奇妙的东西啊!

所以本文就注解与自己定义的注解处理器来学习注解。项目Github地址

基础知识

大家应该知道元注解@Retention吧,它表示注解在什么时候存在,能够分为3个时期:

  • RetentionPolicy.SOURCE:表示注解仅仅在java文件中面才有,在编译的时候。这部分注解就会被擦出。相似于@Override仅仅是给程序猿看的。

  • RetentionPolicy.CLASS:注解在编译的时候会存在。在执行的时候就会擦除。
  • RetentionPolicy.RUNTIME:在执行的时候会存在。这里就要用到了反射来实现了。

Annotation Processor

注解处理器。在编译期间,JVM会自己主动执行注解处理器(当然。我们须要将其注冊)。尽管我们写的Java代码被编译成class就不能被修改了,可是注解处理器会又一次生成其它的java代码。我们能够通过反射来调用新生成的java文件中面的类或方法。然后JVM再对这些生成的java代码进行编译。

这是一个递归的过程。

Java API为我们提供了注解处理器的接口。我们仅仅要实现它就能够了。这个类就是AbstractProcessor,以下就一起来实现它。这个样例我是从【Java二十周年】Java注解处理器,參考过来的。

实现功能例如以下:对于一个类,仅仅要给它的某个加上一个注解,就会自己主动生成其接口。接口中的方法就是加注解的方法。

  1. public class Man {
  2. @Interface("ManInterface")
  3. public void eat() {
  4. System.out.println("Eat");
  5. }
  6. }

eat方法加上了@Interface("ManInterface"),上述就会生成:

  1. public interface ManInterface {
  2. void eat();
  3. }

就是这么粗暴。

然后我们看看注解是怎么定义的:

  1. package wqh.core;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * Created on 2016/8/10.
  8. *
  9. * @author 王启航
  10. * @version 1.0
  11. */
  12. @Target(ElementType.METHOD)
  13. @Retention(RetentionPolicy.CLASS)
  14. public @interface Interface {
  15. String value();
  16. }

注意上面@Target(ElementType.METHOD)表示该方法仅仅能加在方法上面;@Retention(RetentionPolicy.CLASS)表示是在编译时存在的。

好了接下来就是重点了:

  1. public class InterfaceProcessor extends AbstractProcessor {
  2. @Override
  3. public synchronized void init(ProcessingEnvironment env){ }
  4. @Override
  5. public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
  6. @Override
  7. public Set<String> getSupportedAnnotationTypes() { }
  8. @Override
  9. public SourceVersion getSupportedSourceVersion() { }
  10. }

定义自己的注解处理器

  • init对一些工具进行初始化。

  • process就是真正生成java代码的地方。
  • getSupportedAnnotationTypes表示该注解处理器能够出来那些注解。
  • getSupportedSourceVersion能够出来java版本号

我们首先看看除去process的其它三个方法:

  1. public class InterfaceProcessor extends AbstractProcessor {
  2. private Types typeUtils;
  3. private Elements elementUtils;
  4. private Filer filer;
  5. private Messager messager;
  6. @Override
  7. public synchronized void init(ProcessingEnvironment env) {
  8. super.init(env);
  9. elementUtils = env.getElementUtils();
  10. filer = env.getFiler();
  11. typeUtils = env.getTypeUtils();
  12. messager = env.getMessager();
  13. }
  14. @Override
  15. public boolean process(Set<?
  16. extends TypeElement> annotations, RoundEnvironment env) {
  17. }
  18. @Override
  19. public Set<String> getSupportedAnnotationTypes() {
  20. Set<String> types = new TreeSet<>();
  21. types.add(Interface.class.getCanonicalName());
  22. return types;
  23. }
  24. @Override
  25. public SourceVersion getSupportedSourceVersion() {
  26. return SourceVersion.latestSupported();
  27. }

getSupportedSourceVersion返回近期的版本号。

getSupportedAnnotationTypes返回了我们定义的Interface注解。

init初始化了了4个工具类。以下略微介绍一哈:

Elements:Element代表程序的元素,比如包、类或者方法。每一个Element代表一个静态的、语言级别的构件。它仅仅是结构化的文本。他不是可执行的.能够理解为一个签名

(public class Main 或者 private void fun(int a) {}),大家能够联系到XML的解析,或者抽象语法树 (AST)。

给出以下样例:

  1. package com.example; // PackageElement
  2. public class Foo { // TypeElement
  3. private int a; // VariableElement
  4. private Foo other; // VariableElement
  5. public Foo () {} // ExecuteableElement
  6. public void setA ( // ExecuteableElement
  7. int newA // TypeElement
  8. ) {}
  9. }

并且我们能够通过

  1. aType.getEnclosingElement();

得到其父标签(没有父标签就返回null).能够联系Class.getEnclosingClass这种方法。

上面样例大家都懂了吧!!

本文主要就是对ExecuteableElement进行操作。

以下看看Messager

由于在注解处理器里面不能够抛出Exception!为什么了?由于在编译阶段抛出错误后,注解处理器就不会执行完。也就没什么用了。

所以Message就是为了输出错误信息。

关于FilterTypes本文就不进行解说了。。。。

好!!以下看看process方法

  1. @Override
  2. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
  3. Map<String, AnnotatedClass> classMap = new HashMap<>();
  4. // 得到全部注解@Interface的Element集合
  5. Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);
  6. for (Element e : elementSet) {
  7. if (e.getKind() != ElementKind.METHOD) {
  8. error(e, "错误的注解类型。仅仅有函数能够被该 @%s 注解处理", Interface.class.getSimpleName());
  9. return true;
  10. }
  11. ExecutableElement element = (ExecutableElement) e;
  12. AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
  13. String classname = annotatedMethod.getSimpleClassName();
  14. AnnotatedClass annotatedClass = classMap.get(classname);
  15. if (annotatedClass == null) {
  16. PackageElement pkg = elementUtils.getPackageOf(element);
  17. annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
  18. annotatedClass.addMethod(annotatedMethod);
  19. classMap.put(classname, annotatedClass);
  20. } else
  21. annotatedClass.addMethod(annotatedMethod);
  22. }
  23. // 代码生成
  24. for (AnnotatedClass annotatedClass : classMap.values()) {
  25. annotatedClass.generateCode(elementUtils, filer);
  26. }
  27. return false;
  28. }

首先看看第一部分:

  1. Map<String, AnnotatedClass> classMap = new HashMap<>();

这里就是存储整个project凝视过getSupportedAnnotationTypes返回的注解的Map

在这里我们定义了例如以下的数据结构:

  1. classMap
  2. key -> 一个类的类名
  3. value -> AnnotatedClass
  4. // 这个等下会说到,这里知道是这种关系就好
  5. AnnotatedClass
  6. key -> 一个类的类名
  7. value -> 该类全部加上注解的方法。

第二部分:

  1. // 得到全部注解@Interface的Element集合
  2. Set<? extends Element> elementSet = env.getElementsAnnotatedWith(Interface.class);

如凝视所说,得到全部注解@InterfaceElement集合。

注意这里得到了是Element的集合,也就生成了一个树结构。

第三部分:

  1. for (Element e : elementSet) {
  2. if (e.getKind() != ElementKind.METHOD) {
  3. error(e, "错误的注解类型,仅仅有函数能够被该 @%s 注解处理", Interface.class.getSimpleName());
  4. return true;
  5. }
  6. ExecutableElement element = (ExecutableElement) e;
  7. AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
  8. String classname = annotatedMethod.getSimpleClassName();
  9. AnnotatedClass annotatedClass = classMap.get(classname);
  10. if (annotatedClass == null) {
  11. PackageElement pkg = elementUtils.getPackageOf(element);
  12. annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
  13. annotatedClass.addMethod(annotatedMethod);
  14. classMap.put(classname, annotatedClass);
  15. } else
  16. annotatedClass.addMethod(annotatedMethod);
  17. }

首先全部加上@InterfaceElement进行变量,第一步推断注解是否加在Method之上,假设不是就输出错误信息。

上文说到能够用Messager来输出:

  1. private void error(Element e, String msg, Object... args) {
  2. messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
  3. }

然后

  1. ExecutableElement element = (ExecutableElement) e;
  2. AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);

将其转型为ExecutableElement。由于在上面推断全部的Element都是加在方法上的。这里不会报错。

然后构造AnnotatedMethod,由于面向对象,AnnotatedMethod表示一个加了注解的方法。

在这里我们看看什么是AnnotatedMethod:

  1. package wqh.core;
  2. import javax.lang.model.element.ExecutableElement;
  3. import javax.lang.model.element.TypeElement;
  4. /**
  5. * Created on 2016/8/10.
  6. *
  7. * @author 王启航
  8. * @version 1.0
  9. */
  10. public class AnnotatedMethod {
  11. private ExecutableElement annotatedMethodElement;
  12. private String simpleMethodName;
  13. private String simpleClassName;
  14. public AnnotatedMethod(ExecutableElement annotatedMethodElement) {
  15. this.annotatedMethodElement = annotatedMethodElement;
  16. simpleMethodName = annotatedMethodElement.getSimpleName().toString();
  17. /*
  18. * getEnclosingElement() 能够理解为该标签的父标签.
  19. * {@see Class.getEnclosingClass}
  20. */
  21. TypeElement parent = (TypeElement) annotatedMethodElement.getEnclosingElement();
  22. /*
  23. * Return the fully qualified name of this class or interface.
  24. * {@code java.util.Set<E>} is "java.util.Set"
  25. * {@code java.util.Map.Entry}is "java.util.Map.Entry"
  26. */
  27. simpleClassName = parent.getQualifiedName().toString();
  28. }
  29. public ExecutableElement getAnnotatedMethodElement() {
  30. return annotatedMethodElement;
  31. }
  32. public String getSimpleMethodName() {
  33. return simpleMethodName;
  34. }
  35. public String getSimpleClassName() {
  36. return simpleClassName;
  37. }
  38. }

还是比較简单的哈!!

OK,回到 process

  1. String classname = annotatedMethod.getSimpleClassName();
  2. AnnotatedClass annotatedClass = classMap.get(classname);

构造AnnotatedClass。上面也说到过AnnotatedClass,AnnotatedClass能够表示一个类。里面存储了很多的AnnotatedMethod

以下看看其代码:

  1. package wqh.core;
  2. import com.squareup.javapoet.JavaFile;
  3. import com.squareup.javapoet.MethodSpec;
  4. import com.squareup.javapoet.TypeName;
  5. import com.squareup.javapoet.TypeSpec;
  6. import javax.annotation.processing.Filer;
  7. import javax.lang.model.element.Modifier;
  8. import javax.lang.model.element.VariableElement;
  9. import javax.lang.model.util.Elements;
  10. import java.io.IOException;
  11. import java.util.LinkedList;
  12. import java.util.List;
  13. /**
  14. * Created on 2016/8/10.
  15. *
  16. * @author 王启航
  17. * @version 1.0
  18. */
  19. public class AnnotatedClass {
  20. private String className;
  21. private String packageName;
  22. private List<AnnotatedMethod> methods = new LinkedList<>();
  23. /**
  24. * @param packageName 将要生成的类的包名
  25. * @param generateClassName 将要生成的类的类名
  26. */
  27. public AnnotatedClass(String packageName, String generateClassName) {
  28. this.className = generateClassName;
  29. this.packageName = packageName;
  30. }
  31. // 生成Java代码部分。如今能够不看
  32. public void generateCode(Elements elementUtils, Filer filer) {
  33. TypeSpec.Builder typeBuilder = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC);
  34. for (AnnotatedMethod m : methods) {
  35. MethodSpec.Builder methodBuilder =
  36. MethodSpec.methodBuilder(m.getSimpleMethodName())
  37. .addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
  38. .returns(TypeName.get(m.getAnnotatedMethodElement().getReturnType()));
  39. int i = 1;
  40. for (VariableElement e : m.getAnnotatedMethodElement().getParameters()) {
  41. methodBuilder.addParameter(TypeName.get(e.asType()), "param" + String.valueOf(i));
  42. ++i;
  43. }
  44. typeBuilder.addMethod(methodBuilder.build());
  45. }
  46. JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build();
  47. try {
  48. javaFile.writeTo(filer);
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. public void addMethod(AnnotatedMethod annotatedMethod) {
  54. methods.add(annotatedMethod);
  55. }
  56. }

OK。在process里面:

  1. if (annotatedClass == null) {
  2. PackageElement pkg = elementUtils.getPackageOf(element);
  3. annotatedClass = new AnnotatedClass(pkg.getQualifiedName().toString(), element.getAnnotation(Interface.class).value());
  4. annotatedClass.addMethod(annotatedMethod);
  5. classMap.put(classname, annotatedClass);
  6. } else
  7. annotatedClass.addMethod(annotatedMethod);

就是将加了注解的方法,在同一个类的增加到annotatedClass里面去,然后构成classMap

到这里。全部的结构都已生成。也就是:

  1. classMap
  2. key -> 一个类的类名
  3. value -> AnnotatedClass
  4. // 这个等下会说到。这里知道是这种关系就好
  5. AnnotatedClass
  6. key -> 一个类的类名
  7. value -> 该类全部加上注解的方法。

下一步就是生成Java代码了。这里用了JavaWriter的改进版本号JavaPoet,能够自己去下这个包

  1. // 代码生成
  2. for (AnnotatedClass annotatedClass : classMap.values()) {
  3. annotatedClass.generateCode(elementUtils, filer);
  4. }
  5. return false;

这部分不做具体解说。

好了,全部的InterfaceProcessor解说完成。

注冊打包

接下来将其注冊到JVM中去:在Intelilj IDEA,有例如以下的文件夹结构:

javax.annotation.processing.Processor中定义了注解处理器:

  1. wqh.core.InterfaceProcessor

这些都做了之后。就回去生成jar包!!

!能够用Maven,可是我们这个项目加了依赖项,所以有点麻烦。

这里直接用Intelilj IDEA打包!

File->Project Structure能够配置这样

将右側的文件夹加到左側就好。

Build一哈,就能够,能够生成jar包了。!

这些步骤。我们也能够直接使用Google的AutoService,直接省去了这一步。

測试

在其它项目中依照以下配置:



  1. package test;
  2. import wqh.core.Interface;
  3. /**
  4. * Created on 2016/8/10.
  5. *
  6. * @author 王启航
  7. * @version 1.0
  8. */
  9. public class Man {
  10. @Interface("ManInterface")
  11. public void eat() {
  12. System.out.println("Eat");
  13. }
  14. }

执行就能够在generated文件夹看到生成的接口

  1. package test;
  2. public interface ManInterface {
  3. void eat();
  4. }

那么怎么使用MainInterface呢?

  1. package test;
  2. /**
  3. * Created on 2016/8/10.
  4. *
  5. * @author 王启航
  6. * @version 1.0
  7. */
  8. public class Main {
  9. public static void main(String args[]) throws ClassNotFoundException {
  10. Class<?
  11. > c = Class.forName("test.ManInterface");
  12. System.out.println(c.getName());
  13. }
  14. }

尽管这个功能没什么用啊!

。。!

參考

Java二十周年 Java注解处理器

Java注解处理器

我的邮箱:1906362072@qq.com

大二学生,欢迎联系。

Java注解与自己定义注解处理器的更多相关文章

  1. 深入理解Java:注解(Annotation)自己定义注解入门

    深入理解Java:注解(Annotation)自己定义注解入门 要深入学习注解.我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前.我们就必须要了解Java为我们提供的元注解和相关定义注解的 ...

  2. Java深入 - 深入 Java自己定义注解

    我们在使用Spring框架的时候,会常常使用类似:@Autowired 这种注解. 我们也能够自定义一些注解.Java的注解主要在包:java.lang.annotation中实现. 1. 元注解 什 ...

  3. 深入JAVA注解(Annotation):自定义注解 (转)

    原文出自:http://blog.csdn.net/yjclsx/article/details/52101922 一.基础知识:元注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义 ...

  4. java基础知识:自定义注解

    转自 深入了解注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解的作用就是负责注解其他注解.J ...

  5. Java编程的逻辑 (85) - 注解

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  6. 廖雪峰Java4反射与泛型-2注解-2定义注解

    1.定义注解 使用@interface定义注解Annotation 注解的参数类似无参数方法 可以设定一个默认值(推荐) 把最常用的参数命名为value(推荐) 2.元注解 2.1Target使用方式 ...

  7. 怎样自己定义注解Annotation,并利用反射进行解析

    Java注解可以提供代码的相关信息,同一时候对于所注解的代码结构又没有直接影响.在这篇教程中,我们将学习Java注解,怎样编写自己定义注解.注解的使用,以及怎样使用反射解析注解. 注解是Java 1. ...

  8. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

  9. java学习第七天注解.day19

    注解 可以使用注解来修饰类中的成员信息 "注解,可以看作是对 一个 类/方法 的一个扩展的模版 元注解 注解:用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能 ...

随机推荐

  1. bzoj 5294: [Bjoi2018]二进制

    Description pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是333 的倍数.他想研究对于二进 制,是否也有类似的性质.于是他生成了一个长为n 的二进制串,希望 ...

  2. 【次短路径/SPFA】BZOJ1726-[Usaco2006 Nov]Roadblocks第二短路

    [题目大意] 求无向图点1到n的次短路. [思路] 一年多前写过一次堆优化Dijkstra的,方法就是一边跑Dijsktra一边就把次短路径保存下来.和一般Dijkstra不同的是把vis数组去掉了, ...

  3. JxBrowser概述与简单应用

    Q:JxBrowser是什么? JxBrowser是一个跨平台的Java库,允许将基于Google Chromium的Web浏览器组件集成到Java Swing / AWT / JavaFX应用程序中 ...

  4. bzoj 2055: 80人环游世界 -- 上下界网络流

    2055: 80人环游世界 Time Limit: 10 Sec  Memory Limit: 64 MB Description     想必大家都看过成龙大哥的<80天环游世界>,里面 ...

  5. trie--- POJ 3764 The xor-longest Path

    The xor-longest Path Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 5453   Accepted: 1 ...

  6. Codeforces Round #302 (Div. 2) C. Writing Code 简单dp

    C. Writing Code Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/544/prob ...

  7. 程序员应该知道的几个国外IT网站

    程序员应该知道的几个国外IT网站   摘要:文中总结了几个常用的国外IT网站,下面列举出来供大家学习参考: 导读:文中总结了几个常用的国外IT网站,下面列举出来供大家学习参考: 1. TheServe ...

  8. miniSpartan6, another Spartan 6 Kit

    http://thehardwarer.com/2013/05/minispartan-6-another-spartan-6-kit/ miniSpartan6 is an Opens Source ...

  9. GCC安装UBUNTU

    在Ubuntu下安装GCC和其他一些Linux系统有点不一样. 方法一: 该方法超简单:sudo apt-get  build-depgcc 就上面这条命令就可以搞定 方法二:sudo apt-get ...

  10. 字符串转base64,base64转字符串

    [JavaScript原生提供两个Base64相关方法] btoa():字符串或二进制值转为Base64编码 atob():Base64编码转为原来的编码 备注:利用这两个原生方法,我们来封装一下,标 ...