Java注解之编译时注解
编译时注解指的是:@Retention(RetentionPolicy.CLASS)的注解
需要继承 AbstractProcessor 实现注解处理器
需要在build.gradle中
implementation 'com.google.auto.service:auto-service:1.0-rc6' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
并且在使用注解的module中引用注解处理器模块
annotationProcessor project(':anno')
AbstractProcessor
是一个抽象类,实现注解处理器必须继承它,下面主要介绍里面的几个常用方法。
init(ProcessingEnvironment processingEnv)
初始化方法,用于获取一些有用的系统工具类,如Elements, Filer, Messager,Types等;
ProcessingEnvironment
初始方法作为参数传入,源码如下:
public interface ProcessingEnvironment { Map<String, String> getOptions(); Messager getMessager(); Filer getFiler(); Elements getElementUtils(); Types getTypeUtils(); SourceVersion getSourceVersion(); Locale getLocale(); }
-
getOptions:用来接收外部自定义参数的,后面getSupportedOptions方法中会介绍使用
-
getMessager:返回一个Messager对象,作为日志打印工具。看到有人说AnnotationProcessor是运行在javac期间,不能用System.out.print打印,其实System.out.print也是可以的,但是processingEnv.getMessager()功能更强大,可以区分Log类型,且Log为 Error类型时,会直接终止程序。
-
messager = processingEnvironment.getMessager(); messager.printMessage(Diagnostic.Kind.NOTE, "init");
日志类型有:
-
public static enum Kind { ERROR, WARNING, MANDATORY_WARNING, NOTE, OTHER; private Kind() { } }
执行Rebuild Project可以在Build窗口看到如下日志
-
getFiler:返回一个Filer对象,主要负责生成文件。源码如下
-
public interface Filer { JavaFileObject createSourceFile(CharSequence var1, Element... var2) throws IOException; JavaFileObject createClassFile(CharSequence var1, Element... var2) throws IOException; FileObject createResource(Location var1, CharSequence var2, CharSequence var3, Element... var4) throws IOException; FileObject getResource(Location var1, CharSequence var2, CharSequence var3) throws IOException; }
-
-
getElementUtils:返回一个Elements对象,和Element相关的工具类。比如我们要获取包名怎么办?可以通过上面介绍过的getEnclosingElement方法一层一层往上找,非常麻烦也很容易出错。还可以通过Elements中的getPackageOf方法直接获取到
-
getTypeUtils:返回一个Types对象,和元素类型相关的工具类
-
getSourceVersion:支持的Java版本
-
getLocale:返回Locale对象,这个没什么可说的,就是国际化的东西
-
-
getSupportedOptions()
这个方法允许我们自定义一些参数传给Processor,就拿ARouter举例子
@Override public Set<String> getSupportedOptions() { return new HashSet<String>() {{ this.add(KEY_MODULE_NAME); this.add(KEY_GENERATE_DOC_NAME); }}; }
然后在gralde文件中的传一个参数
android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } }
最后在Processor的init方法中获取参数
@Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); ... // Attempt to get user configuration [moduleName] Map<String, String> options = processingEnv.getOptions(); if (MapUtils.isNotEmpty(options)) { moduleName = options.get(KEY_MODULE_NAME); } ... }
getSupportedSourceVersion()
设置支持的java版本,一般返回最近版本就行。
@Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
也可以使用@SupportedSourceVersion
注解完成。
@SupportedSourceVersion(SourceVersion.RELEASE_8) public class TestProcessor extends AbstractProcessor { ... }
getSupportedAnnotationTypes()
设置支持的注解类型,只有在这个方法中添加过的注解才会被注解处理器所处理。
比如你自定义了个@TestAnnotation
注解
@Override public Set<String> getSupportedAnnotationTypes() { HashSet<String> set = new HashSet<>(); set.add(TestAnnotation.class.getCanonicalName()); return set; }
也可以用@SupportedAnnotationTypes
注解完成
@SupportedAnnotationTypes("com.wzc.annotation.TestAnnotation") public class TestProcessor extends AbstractProcessor { ... }
process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
所有关于注解的处理和java文件的生成都是在这个方法中完成,当返回值为true的时候表示这个Processor处理的注解不会再被后续的Processor处理。如果返回false,则表示这些注解还会被后续的Processor处理,类似拦截器模式。
参数
- TypeElementSet:所有待处理的的注解集合。(一般用不上)
- roundEnvironment:表示当前注解所处的环境,通过这个参数可以查询到当前这一轮注解处理的信息。主要使用
roundEnvironment.getElementsAnnotatedWith(TestAnnotation.class)
方法获取我们自定义注解标注的所有元素集合,是个Set<? extends Element>
Element
所有被注解标注的部分都会被解析成element,element既可能是类,也可能是类属性,还可能是方法,这就要看你使用自定义注解注解了那些东西了。获取到element之后我们还需要将element转换成对应的子类。
- ExecutableElement: 可执行元素,包括类或者接口的方法。
- PackageElement: 包元素
- TypeElement:类,接口,或者枚举。
- VariableElement: 类属性,枚举常量,方法参数,局部变量或者异常参数。
- TypeParameterElement: 表示一个泛型元素
package com.wzc.gradle.myaptdemo; // PackageElement // TypeElement public class TestClass { // VariableElement private String mVariableElement; // ExecutableElement public TestClass(String mVariableElement // TypeElement) { this.mVariableElement = mVariableElement; } // ExecutableElement public static void main(String[] args //TypeElement) { } }
我们在定义注解的时候可以指定注解的ElementType,这个ElementType和Element是有对应关系的,通过测试可得到下面表格。
注解的ElementType | 注解处理器的Element |
---|---|
TYPE | TypeElement |
FIELD | VariableElement |
METHOD | ExecutableElement |
PARAMETER | VariableElement |
CONSTRUCTOR | ExecutableElement |
LOCAL_VARIABLE | 获取不到 |
ANNOTATION_TYPE | TypeElement |
PACKAGE | PackageElement |
TYPE_PARAMETER | TypeParameterElement |
TYPE_USE | 1对多,取决于使用的位置 |
拿到对应Element之后,还需要收集Element的相关信息,下面我们介绍几个常用的方法
- getSimpleName:获取该元素的名字;
- getModifiers:获取该元素的访问权限,返回一个Set;
- asType: 获取该元素的类型,比如String会返回java.lang.String,TextView会返回android.widget.TextView;
- getEnclosingElement:获取父级元素,比如参数的父级是方法,方法的父级是类或者接口;
- getQualifiedName:获取全限定名,如果是类的话,包含完整的报名路径;