Java注解之编译时注解
Posted wuzuchang2022
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java注解之编译时注解相关的知识,希望对你有一定的参考价值。
文章目录
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类型时,会直接终止程序。
@Override public synchronized void init(ProcessingEnvironment processingEnv) super.init(processingEnv); messager = processingEnv.getMessager(); messager.printMessage(Diagnostic.Kind.WARNING, "TestAnnotationProcessor >>> warning"); messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, "TestAnnotationProcessor >>> mandatory_warning"); messager.printMessage(Diagnostic.Kind.NOTE, "TestAnnotationProcessor >>> note"); messager.printMessage(Diagnostic.Kind.OTHER, "TestAnnotationProcessor >>> other"); //messager.printMessage(Diagnostic.Kind.ERROR, "TestAnnotationProcessor >>> error"); //Error类型,会终止程序
执行Rebuild Project可以在Build窗口看到如下日志
-
getFiler:返回一个Filer对象,主要负责生成文件。源码如下:
public interface Filer // 创建源文件 JavaFileObject createSourceFile(CharSequence var1, Element... var2) throws IOException; // 创建class文件 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 //T ypeElement)
我们在定义注解的时候可以指定注解的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:获取全限定名,如果是类的话,包含完整的报名路径;
这些api大家可以自己去试一试。
接下来我们在process方法中来生成个简单的文件
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
for (Element element : roundEnvironment.getElementsAnnotatedWith(TestAnnotation.class))
BufferedWriter bufferedWriter = null;
try
VariableElement variableElement = (VariableElement) element;
System.out.println("variableElement >>> getSimpleName = " + variableElement.getSimpleName());
boolean kind = variableElement.getKind() == ElementKind.FIELD;
System.out.println("variableElement >>> getKind = " + kind);
System.out.println("variableElement >>> asType = " + variableElement.asType().toString());
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
System.out.println("typeElement >>> getSimpleName = " + typeElement.getSimpleName());
System.out.println("typeElement >>> getQualifiedName = " + typeElement.getQualifiedName());
boolean typeElementKind = typeElement.getKind() == ElementKind.METHOD;
System.out.println("typeElement >>> getKind = " + typeElementKind);
System.out.println("typeElement >>> getModifiers = " + typeElement.getModifiers().toString());
PackageElement packageElement = (PackageElement) typeElement.getEnclosingElement();
System.out.println("typeElement >>> getSimpleName = " + packageElement.getSimpleName());
System.out.println("typeElement >>> getQualifiedName = " + packageElement.getQualifiedName());
boolean packageElementKind = packageElement.getKind() == ElementKind.CLASS;
System.out.println("typeElement >>> getKind = " + packageElementKind);
System.out.println("typeElement >>> getModifiers = " + packageElement.getModifiers().toString());
JavaFileObject jfo = filer.createSourceFile("com.wzc.demo.HelloWorld");
bufferedWriter = new BufferedWriter(jfo.openWriter());
bufferedWriter.append("package ").append(packageElement.getQualifiedName()).append(";\\n");
bufferedWriter.append("public class ").append("HelloWorld \\n");
bufferedWriter.newLine();
bufferedWriter.append("public static void main(String[] args) \\n");
bufferedWriter.newLine();
bufferedWriter.append("System.out.println(\\"Hello, World!\\");\\n");
bufferedWriter.newLine();
bufferedWriter.append("");
bufferedWriter.newLine();
bufferedWriter.append("");
bufferedWriter.flush();
catch (Exception e)
e.printStackTrace();
finally
if (bufferedWriter != null)
try
bufferedWriter.close();
catch (IOException e)
e.printStackTrace();
return false;
推荐使用使用开源库JavaPoet来实现文件生成。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
try
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(filer);
catch (IOException e)
e.printStackTrace();
return false;
执行Rebuild Project我们可以在app\\build\\generated\\ap_generated_sources\\debug\\out\\文件夹下看到我们生成的类
实战
参考ARouter、butterknife项目结构都是
- app :Demo
- api:
Android library
,SDK对外暴露的api - annotations:
java library
,存放自定义注解 - compiler:
java library
,存放注解处理器
我们也根据这个结构新建自己的项目
这里需要注意 Android library
和 java library
的区别
自定义注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView
/**
* 控件变量的resourceId
*/
int value();
注解处理器
注解处理器library需要用到两个第三方库auto-service和javapoet
dependencies
implementation project(':findview-annotation')
//使用AutoService注解,这里使用compileOnly就行,因为AutoService注解生命周期是在编译期的,具体可以看源码:https://github.com/google/auto/blob/master/service/annotations/src/main/java/com/google/auto/service/AutoService.java
compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
//AutoService注解处理器用于自动为 JAVA Processor 生成 META-INF 信息。
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
//快速生成.java文件的库
implementation 'com.squareup:javapoet:1.10.0'
auto-servic作用是帮我们自动生成 META-INF 信息,我们只有在我们注解处理器上加上如下代码:
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor
他就会帮我们生成如下文件
如果不使用AutoService,我们则需要自己libray中新建目录src/main/resources/META-INF/services,在该目录下下创建一个javax.annotation.processing.Processor文件,文件内写入你的注解处理器Processor的全类名。为了方便我们直接使用AutoService,替我们生成。
package com.wzc.findview.compiler;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.wzc.findview.annotation.BindView;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor
/**
* 生成文件的工具类
*/
private Filer filer;
/**
* 打印信息
*/
private Messager messager;
/**
* 元素相关
*/
private Elements elementUtils;
private Types typeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
super.init(processingEnv);
以上是关于Java注解之编译时注解的主要内容,如果未能解决你的问题,请参考以下文章
Java注解之RetentionDocumentedInheritedtarget介绍
Java注解-注解处理器(编译期|RetentionPolicy.SOURCE)