diff --git a/blogs/Android/APT.md b/blogs/Android/APT.md index fae1ec6..1b6d1b1 100644 --- a/blogs/Android/APT.md +++ b/blogs/Android/APT.md @@ -1,24 +1,57 @@ --- -APT、JavaPoet 实现 ButterKnife +APT --- #### 目录 1. 思维导图 2. 概述 -3. APT -4. AutoService -5. JavaPoet -6. ButterKnife 的实现 -7. 参考 +3. 实现原理 +4. APT + - Processor + - init + - getSupportedAnnotationTypes + - getSupportedSourceVersion + - process + - Element + - TypeElement + - ExecutableElement + - VariableElement + - RoundEnvironment + - getElementsAnnotatedWith(BindView.class) +5. AutoService +6. JavaPoet +7. ButterKnife 的实现 + - APT + - AutoService + - JavaPoet +8. 参考 #### 思维导图 -![](https://i.loli.net/2019/01/26/5c4c00a69d7a5.png) +![](https://i.loli.net/2019/01/29/5c5015c40825d.png) #### 概述 -用 APT、JavaPoet、AutoService 实现简单的 ButterKnife,APT 负责处理编译时注解,JavaPoet 用于生成 Java 代码,AutoService 负责注册注解处理器。 +APT 即注解处理器,它有三个主要用途:一是定义编译规则,并检查被编译的源文件;二是修改已有的源代码;三是生成新的源代码;其中,第二种涉及了 Java 编译器的内部 API,可以会存在兼容性问题,所以并不推荐,第三种较为常见。 + +这节用 APT、JavaPoet、AutoService 实现简单的 ButterKnife,APT 负责处理编译时注解,JavaPoet 用于生成 Java 代码,AutoService 负责注册注解处理器。 + +#### 实现原理 + +在介绍注解处理器之前,我们先来了解一下 Java 编译器的工作流程。 + +![](https://i.loli.net/2019/01/29/5c4fe444add11.png) + +如上图所示,Java 源代码的编译过程可以分为三个步骤: + +1. 将源文件解析成抽象语法树 +2. 调用已注册的注解处理器 +3. 生成字节码 + +如果在第二步调用注解处理器过程中生成了新的源文件,那么编译器将重复第一二步骤,解析并处理新生成的源文件。 + +所以可以这样理解,我们写的自定义注解处理器是给编译器写的,让它按照我们的逻辑来处理注解,所以也得向编译器注册注解处理器。 #### APT 注解处理器 @@ -26,6 +59,158 @@ APT(Annotation Processing Tool)即注解处理器,是一种注解处理工 Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解处理逻辑。APT 的原理是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者在根据注解元素在编译期输出对应的 Java 代码。 +##### Processor + +所有的注解处理器都需要实现接口 Processor,AbstractProcessor 也是实现了该接口,对开发者更友好。 + +```java +public interface Processor { + + void init(ProcessingEnvironment processingEnv); + + Set getSupportedAnnotationTypes(); + + SourceVersion getSupportedSourceVersion(); + + boolean process(Set annotations, RoundEnvironment roundEnv); + + ... +} +``` + +它有四个重要方法,其中 init 方法用于存放注解处理器的初始化代码,之所以不用构造器,是因为在 Java 编译器中,注解处理器的实例是通过反射 API 生成的,也正是因为使用反射 API,每个注解处理器类都需要定义一个无参构造器。 + +通常来说,当编写注解处理器时,我们不声明任何构造器,并依赖于 Java 编译器,而具体的初始化代码,则放入 init 方法之中。 + +而剩下的三个方法中,getSupportedAnnotationTypes 方法将返回注解处理器所支持的注解类型,这些注解类型只需要用字符串形式表示即可。 + +getSupportedSourceVersion 方法将返回该处理器所支持的 Java 版本,通常直接返回 SourceVersion.latestSupported(),而 process 方法则是最为关键的注解处理方法。 + +process 方法接收两个参数,分别代表该注解处理器所能处理的注解类型,以及囊括当前轮生成的抽象语法树的 RoundEnvironment。 + +通常我们这样使用 RoundEnvironment: + +```java +for (Element element : env.getElementsAnnotatedWith(BindView.class)) { + //todo +} +``` + +这个 Element 表示一个程序元素,可以是包、类、或者方法,所有通过注解取得的元素都将以 Element 类型处理,准确来说是 Element 对象的子类处理。 + +Element 的子类: + +- ExecutableElement + + 表示某个类或接口的方法、构造方法或初始化程序,包括注释类型元素。 + + 对应注解是 ElementType.METHOD 和 ElementType.CONSTRUCTOR。 + +- PackageElement + + 表示一个包程序元素,提供对有关包及其成员的信息访问。 + + 对应注解是 ElementType.PACKAGE。 + +- TypeElement + + 表示一个类或接口程序元素,提供对有关类型及其成员的信息访问。 + + 对应注解是 ElementType.TYPE。 + + 注意:枚举类型是一种类,而注解类型是一种接口。 + +- TypeParameterElement + + 表示类、接口、方法元素的类型参数。 + + 对应注解是 ElementType.PARAMETER。 + +- VariableElement + + 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。 + + 对应注解是 ElementType.FIELD 和 ElementType.LOCAL_VARIABLE。 + +**不同类型的 Element 的信息获取方式不同。** + +```java +@AutoService(Processor.class) +public class InfoProcessor extends AbstractProcessor { + + private Elements mElementsUtils; + + @Override + public synchronized void init(ProcessingEnvironment processingEnvironment) { + super.init(processingEnvironment); + mElementsUtils = processingEnvironment.getElementUtils(); + } + + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment) { + + //解析类上的注解 + for (Element element : roundEnvironment.getElementsAnnotatedWith(Info.class)) { + TypeElement classElement = (TypeElement) element; + PackageElement packageElement = (PackageElement) element.getEnclosingElement(); + //全类名 + System.out.println(classElement.getQualifiedName().toString()); + //类名 + System.out.println(classElement.getSimpleName().toString()); + //包名 + System.out.println(packageElement.getQualifiedName().toString()); + //父类名 + System.out.println(classElement.getSuperclass().toString()); + } + + //解析方法上的注解 + for (Element element : roundEnvironment.getElementsAnnotatedWith(Info.class)) { + ExecutableElement executableElement = (ExecutableElement) element; + TypeElement classElement = (TypeElement) executableElement.getEnclosingElement(); + PackageElement packageElement = mElementsUtils.getPackageOf(classElement); + //全类名 + String fullClassName = classElement.getQualifiedName().toString(); + //与上面一致 + //... + //方法名 + String methodName = executableElement.getSimpleName().toString(); + + //方法参数列表 + List methodParameters = executableElement.getParameters(); + List types = new ArrayList<>(); + for (VariableElement variableElement : methodParameters) { + TypeMirror methodParameterType = variableElement.asType(); + if (methodParameterType != null) { + TypeVariable typeVariable = (TypeVariable) methodParameterType; + methodParameterType = typeVariable.getUpperBound(); + } + //参数名 + String parameterName = variableElement.getSimpleName().toString(); + //参数类型 + String parameteKind = methodParameterType.toString(); + types.add(methodParameterType.toString()); + } + } + + //解析属性上的注解 + for (Element element : roundEnvironment.getElementsAnnotatedWith(Info.class)) { + VariableElement variableElement = (VariableElement) element; + TypeElement classElement = (TypeElement) element.getEnclosingElement(); + PackageElement packageElement = mElementsUtils.getPackageOf(classElement); + //类名 + String className = classElement.getSimpleName().toString(); + //与上面一致 + //... + + //类成员类型 + TypeMirror typeMirror = variableElement.asType(); + String type = typeMirror.toString(); + } + return true; + } +} +``` + ##### AbstractProcessor 实现一个注解处理器,需要继承 AbstractProcessor ,如下: @@ -87,32 +272,6 @@ Filter:用于给注解处理器创建文件; Messager:用于给注解处理器报告错误、警告、提示等信息。 -##### Element 元素相关 - -注解处理器工具扫描 Java 源文件,源文件中的每一部分都是程序中的 Element 元素,如包、类、方法、字段等。例如源代码中的类声明信息代表 TypeElement 类型元素,方法声明信息代表 ExecutableElement 类型元素,有了这些结构,就能完整的表示整个源代码信息了。 - -Element 元素分为以下类型: - -1. ExcecutableElement - - 可执行元素,包括类或接口的方法、构造方法或初始化程序。 - -2. PackageElement - - 包元素,提供对有关包及其成员的信息的访问。 - -3. TypeElement - - 类或接口元素,提供对有关类型及其成员的信息的访问。 - -4. TypeParameterElement - - 表示一般类、接口、方法或构造方法元素的形式类型参数,类型参数声明一个 TypeVariable - -5. VariableElement - - 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。 - #### AutoServcie 注册注解处理器 以前要注册注解处理器要在 module 的 META_INFO 目录新建 services 目录,并创建一个名为 Java.annotation.processing.Processor 的文件,然后在文件中写入要注册的注解处理器的全民。 @@ -302,6 +461,8 @@ public class ButterKnife { #### 参考 +[注解处理器](https://time.geekbang.org/column/article/40189) + [教你实现一个轻量级的注解处理器 APT](https://mp.weixin.qq.com/s/3zrAzOUGpovRRbuYnce3uw) [拆 JakeWharton 系列之 ButterKnife](https://juejin.im/post/58f388d1da2f60005d369a09) diff --git a/blogs/注解.md b/blogs/注解.md index ce2d7d3..ea0a08d 100644 --- a/blogs/注解.md +++ b/blogs/注解.md @@ -7,11 +7,26 @@ 1. 思维导图 2. 定义 3. 元注解 + - @Retention + - @Target + - @Inherited + - @Documented + - @Repeatable 4. 自定义注解 -5. 参考 + - 运行时注解 + - 反射解析 + - 编译时注解 + - APT 解析 + - 运行原理 +5. 常用注解 + - Android 注解 + - Java 注解 +6. 参考 #### 思维导图 +![](https://i.loli.net/2019/01/29/5c501a8a592e2.png) + #### 定义 注解是 JDK 5 引入的,注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。 @@ -22,16 +37,17 @@ Annotation 不能运行,它只有成员变量,没有方法。Annotation 和 #### 元注解 -元注解可以理解为注解上的注解,有四个: +元注解可以理解为注解上的注解,有五个: - @Retention - @Target - @Inherited - @Documented +- @Repeatable ##### @Retention -定义该 Annotation 被保留的策略。 +定义该 Annotation 的生命周期。 - RetentionPoicy.SOURCE @@ -47,10 +63,10 @@ Annotation 不能运行,它只有成员变量,没有方法。Annotation 和 ##### @Target -定义了 Annotation 所修饰的对象范围。 +定义了 Annotation 所修饰的对象结构。 - ElementType.CONSTRUCTOR 用于描述构造器 -- ElementType.FIELD 用户描述域 +- ElementType.FIELD 用户描述属性 - ElementType.LOCAL_VARIABLE 用于描述局部变量 - ElementType.METHOD 用于描述方法 - ElementType.PACKAGE 用于描述包 @@ -66,10 +82,167 @@ Annotation 不能运行,它只有成员变量,没有方法。Annotation 和 是否会保存到 Javadoc 文档中。 +##### @Repeatable + +JDK1.8新加的,表明当自定义注解的属性值有多个时,自定义注解可以多次使用。比如当成员变量是一个注解数组时: + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Repeatable(Infos.class) +public @interface Info { + String value(); +} +``` + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Infos { + Info[] value(); +} +``` + +看到这,其实 Repeatable 修饰的 Infos 就可以看成是一个盛放 Info 注解的容器,这里需要注意的是,Info 和 Infos 修饰的 Target 一定要一致,下面就是取注解: + +```java +@Info("Test 1") +@Info("Test 2") +public class AnnotationTest { + + public static void main(String[] args) throws NoSuchFieldException { + if (AnnotationTest.class.isAnnotationPresent(Infos.class)) { + Infos infos = AnnotationTest.class.getAnnotation(Infos.class); + for (Info info : infos.value()) { + System.out.println(info.value()); + } + } + } +} +``` + #### 自定义注解 +自定义注解可以根据有无成员变量分为标记型注解和元数据注解。定义了注解之后,还需要解析注解。解析注解也可以分为两种,它是根据 Retention 来划分的,如果是编译时注解,可以采用 APT 来解析,如果是运行时注解,则采用反射来获取。 + +##### 运行时注解 + +定义一个运行时注解,可以标注在类上、方法上、或者属性上。正如我们前面说的,如果不写,则可以修饰所有。 + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) +public @interface Info { + String value(); +} +``` + +通过反射处理该注解: + +```java +@Info("Test Class") +public class AnnotationTest { + + @Info("Test field") + private String id; + + @Info("Test method") + public void test() { + + } + + public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException { + + //解析类上的注解 + if (AnnotationTest.class.isAnnotationPresent(Info.class)) { + Info infoClass = AnnotationTest.class.getAnnotation(Info.class); + System.out.println(infoClass.value()); + } + + //解析方法上的注解 + Info infoMethod = AnnotationTest.class.getMethod("test").getAnnotation(Info.class); + System.out.println(infoMethod.value()); + + //解析成员变量上的注解 + Field field = AnnotationTest.class.getDeclaredField("id"); + field.setAccessible(true); + Info infoField = field.getAnnotation(Info.class); + System.out.println(infoField.value()); + + } +} +``` + +这里涉及到的就是反射的知识了,在回顾一下好了。利用反射解析注解主要有以下三个方法: + +1. boolean isAnnotationPresent(Class annotation) + + 判断是否使用了某个注解。 + +2. \ A getAnnotation(Class\ annotation) + + 获得指定的注解元素。 + +3. Annotation[] getAnnotations() + + 返回对应元素上的所有注解。 + +##### 编译时注解 + +编译时注解的解析,是通过 APT 来实现的。我们可以通过继承 AbstractProcress 来自定义注解处理逻辑,但是还得需要向编译器注册注解处理器,这是一件很麻烦的事,需要在 META-INFO 目录下手动注册,一般是通过依赖 Google 的 AutoService 库来解决。 +APT 在前面已经写过了,可以参考:[APT](https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/APT.md) + +运行原理: + +Java 源代码的编译流程分为三个步骤: + +1. 将源文件解析成抽象语法树 +2. 调用已注册的注解处理器 +3. 生成字节码 + +如果第二步调用注解处理器过程中生成了新的源文件,那么编译器将重复第一二步骤,解析并处理新生成的源文件。 + +所以可以这样理解,我们写的自定义注解处理器是给编译器写的,让它按照我们的逻辑来处理注解,所以也得向编译器注册注解处理器。 + +#### 常用注解 + +##### Android 常用注解 + +在 android.support.annotation 包中提供了很多好用的注解,比如: + +- 资源类型注解 + + 用来标注元素必须为指定的资源类型,例如 @AnimRes、@ColorRes、@LayoutRes 等等。 + +- 空值注解 + + @Nullable 可空类型,@NonNull 不可空类型。 + +- 值约束注解 + + 即约束元素取值范围,如 @IntRange、@FloatRange。 + +- 权限注解 + + 检查某操作是否有必要的权限,比如@RequiresPermission。 + +- 线程注解 + + 可以标注方法、类、接口等只能在指定的线程被调用,例如 @MainThread、@UIThread、@WorkerThread 等。 + +##### Java 常用注解 + +- @Override +- @Deprecated +- @SuppressWarnings #### 参考 -[浅谈Android下的注解](https://juejin.im/post/5b292b736fb9a00e8c4435f7) \ No newline at end of file +[浅谈Android下的注解](https://juejin.im/post/5b292b736fb9a00e8c4435f7) + +[Android Annotation扫盲笔记](https://juejin.im/post/5a771b8b6fb9a0633c65e947) + +[注解处理器](https://time.geekbang.org/column/article/40189) + +[注解浅析](https://www.jianshu.com/p/937a15e18eac) \ No newline at end of file diff --git a/images/Android/ButterKnife.png b/images/Android/ButterKnife.png deleted file mode 100644 index 1a891ba..0000000 Binary files a/images/Android/ButterKnife.png and /dev/null differ diff --git a/images/Java/APT.png b/images/Java/APT.png new file mode 100644 index 0000000..bedd9b3 Binary files /dev/null and b/images/Java/APT.png differ diff --git a/images/Java/Annotation.png b/images/Java/Annotation.png new file mode 100644 index 0000000..cdc9f2e Binary files /dev/null and b/images/Java/Annotation.png differ diff --git a/images/Java/编译过程.png b/images/Java/编译过程.png new file mode 100644 index 0000000..5428345 Binary files /dev/null and b/images/Java/编译过程.png differ