Java注解知识梳理—自定义注解处理器
Java注解知识梳理—自定义注解处理器
前言
前面介绍了如何自定义注解以及Java中关于注解的一些元素作用,学会了如何创建注解,接下来就需要了解如何使用注解。
一个注解的@Retention属性说明了该注解使用的生命周期范围,我们也知道一共有三个属性值:
- RetentionPolicy.SOURCE 源码注解,注解的声明周期只存在java源码中,javac把java源文件编译成class文件时,就会把这些源码注解去掉,所以class文件中就不会存在这些注解。
- RetentionPolicy.CLASS 编译时注解,javac把java源文件变成class文件时,会把这些注解信息写入class文件中,但是在jvm运行的时候,是不会加载这些注解的。
- RetentionPolicy.RUNTIME 运行时注解,这些注解声明周期最长,一直保持到jvm运行中。
根据注解的声明周期以及java运行的流程节点【java文件 >>〉(javac)>>〉class文件 >>〉(jvm)>>〉执行】我们可以分析出注解的用途会在:javac编译的时候以及jvm运行的时候,其中编译SOURCE、CLASS常用于javac编译阶段,RUNTIME常用于jvm运行阶段,本章我们来介绍如何在编译阶段使用注解。
分析
想在编译阶段使用注解,两个前提条件需要满足:
- 编译前对应代码已经用我们的注解标注好了
- 编译的过程中,对标注好的代码进行分析、做一些逻辑处理
第一个就是创建注解、标注注解,前一章我们已经讲过了,不多说。
第二个需要一个注解处理器(Annotation Processor ,注解处理器是javac的一个工具,存在于Java 5,但是主要API在java 6才加入,主要用来在编译时扫描和处理注解信息)。通过一些配置,这些代码可以在javac编译的时候运行,可以过滤查询出对应的注解信息,然后做一些逻辑处理。
创建demo
经过分析,我们需要掌握的知识就是注解处理器的使用,我们先按照步骤做出一个demo,看一下成果,有一点成就感,然后再逐步梳理对应的知识点。
环境:
- Android Studio 3.3.2(Mac)
- Java 8
- Gradle:gradle-4.10.1-all.zip(项目根目录下gradle文件夹下wrapper中配置的)
- gradle for Android:3.3.2 (这个是build.gradle配置的)
目标:
- 创建一个TestAnnotation注解,可以标注到类上、方法上、变量上,作用范围:RetentionPolicy.SOURCE
- 在MainActitity中的类上、方法上、变量上加上TestAnnotation注解
- 解析出标注了TestAnnotation注解的元素,并将这些元素信息写到一个AutoCreateClass类中的getMessage方法中返回:
public class AutoCreateClass {
public String getMessage() {
return "这里面放一些解析出来的信息";
}
}
跟这我左手右手一步一步写代码
- 创建空白工程项目(只有一个MainActivity)
Android Studio —> File —> New —> New Project—> Empty Activity —> 起名AnnotationApplication
image-20190604182815782.png- 创建注解lib
Android Studio —> File —> New —> New Module—> Java lib—> 起名annotation,并创建TestAnnotation注解类
image-20190604183911476.png image-20190604185145283.pngTestAnnotation注解类:
package com.anonyper.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
//@Target(ElementType.TYPE_USE)//API 26
public @interface TestAnnotation {
}
- 创建注解解释器
Android Studio —> File —> New —> New Module—> Java lib—> 起名annotationprocessor,并创建MyProcessor类(先创建,后面再修改)
image-20190604183911476.png image-20190604185040728.png- 主app引用annotation、annotationprocessor,并标记好MainActivity
app下的build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.anonyper.annotationapplication"
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation project(':annotation')//引入注解
annotationProcessor project(':annotationprocessor')//引入注解解释器
}
MainActivity
/**
* 被注解标注的类
*/
@TestAnnotation
public class MainActivity extends AppCompatActivity {
/**
* 被注解标注的变量
*/
public @TestAnnotation String name = "MainActivity >> name";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 被注解标注的方法
* ElementType.TYPE_USE 的注解不能标注返回void的方法,我们返回一个string
* @param name
* @return
*/
@TestAnnotation
public String mainMethon(String name) {
return "mainMethon";
}
}
- annotationprocessor引入annotation并处理注解逻辑
annotationprocessor下的build.gradle(auto-service先用,后面解释)
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotation')//引入注解信息
implementation 'com.google.auto.service:auto-service:1.0-rc2'//引入自动生成注解配置信息的库
// Adds libraries defining annotations to only the compile classpath.
implementation 'com.squareup:javapoet:1.7.0'//引入生成代码的库(要不我们需要自己拼接字符串,容易出错)
}
sourceCompatibility = "8"
targetCompatibility = "8"
- 修改annotationprocessor下的MyProcessor的代码逻辑
package com.anonyper.annotationprocessor;
import com.anonyper.annotation.TestAnnotation;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.List;
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.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes("com.anonyper.annotation.TestAnnotation")//注解解释器支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8)//注解解释器支持的版本
@AutoService(Processor.class)//自动生成注解配置信息
public class MyProcessor extends AbstractProcessor {
private static final String TAG = "MyProcessor >>> ";
private Filer filerUtils; // 文件写入
private Elements elementUtils; // 操作Element 的工具类
private Messager messagerUtils; //可以在编译时输出信息 用于调试
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filerUtils = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
messagerUtils = processingEnv.getMessager();
messagerUtils.printMessage(Diagnostic.Kind.WARNING, TAG + "Myprocessor init");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messagerUtils.printMessage(Diagnostic.Kind.WARNING, TAG + "Myprocessor process ");
//信息收集器
StringBuilder builder = new StringBuilder();
//获取带有TestAnnotation注解信息的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment
.getElementsAnnotatedWith(TestAnnotation.class);
//遍历
for (Element element : elementsAnnotatedWith) {
//获取元素方法
builder.append("元素名称>>>:" + element.getSimpleName().toString());
switch (element.getKind()) {
case CLASS://类
builder.append(" 是一个类 >> ");
TypeElement typeElement = (TypeElement) element;
//包名信息
builder.append("包名信息是>>>:" + typeElement.getQualifiedName() + "\n");
appendClassInfo(typeElement, builder);
break;
case FIELD://变量
builder.append(" 是一个变量 >> ");
VariableElement variableElement = (VariableElement) element;
builder.append(" 值为 " + variableElement.getConstantValue() + "\n");
break;
case METHOD://方法
builder.append(" 是一个方法 >> ");
ExecutableElement executableElement = (ExecutableElement) element;
appendMethodInfo(executableElement, builder);
break;
default:
break;
}
}
if (builder.toString().length() > 0) {
//创建方法
MethodSpec getMessage = MethodSpec.methodBuilder("getMessage")
.addModifiers(Modifier.PUBLIC)
.returns(String.class)
.addStatement("return $S", builder.toString())
.build();
//创建类
TypeSpec AutoCreateClass = TypeSpec.classBuilder("AutoCreateClass")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(getMessage)
.build();
//创建包名
JavaFile javaFile = JavaFile.builder("com.anonyper.autocreate", AutoCreateClass)
.build();
try {
//开始生成代码
javaFile.writeTo(filerUtils);
} catch (IOException e) {
messagerUtils.printMessage(Diagnostic.Kind.WARNING, TAG + "异常报错:" + e.toString());
// e.printStackTrace();
}
}
messagerUtils.printMessage(Diagnostic.Kind.WARNING, TAG + builder.toString());
return true;
}
/**
* 遍历类元素 获取下面信息
*
* @param typeElement
* @param builder
*/
public void appendClassInfo(TypeElement typeElement, StringBuilder builder) {
if (typeElement == null || builder == null)
return;
List<? extends Element> enclosedElements = typeElement
.getEnclosedElements();
builder.append("该类下面有如下元素:");
for (Element element : enclosedElements) {
builder.append(element.getKind().name() + ">>>" + element.getSimpleName() + " ");
}
builder.append("\n");
}
/**
* 遍历类元素 获取下面信息
*
* @param executableElement
* @param builder
*/
public void appendMethodInfo(ExecutableElement executableElement, StringBuilder builder) {
if (executableElement == null || builder == null)
return;
builder.append("该方法下面有如下参数:\n");
//获取参数
List<? extends VariableElement> parameters = executableElement
.getParameters();
//获取参数类型
List<? extends TypeParameterElement> typeParameters = executableElement
.getTypeParameters();
for (int i = 0; i < parameters.size(); i++) {
builder.append(parameters.get(i).getSimpleName() + " 类型 >> " + parameters.get(i).asType().toString() + "\n");
}
//获取返回值
TypeMirror returnType = executableElement.getReturnType();
builder.append("该方法返回值:>>" + returnType.toString());
}
}
- 开始配置处理器,进行编译
在Android Studio的Gradle — > :app — > Tasks — > other — > complieDebugJavaWithJavac(complieReleaseJavaWithJavac也可以)
image-20190605113608746.png运行日志:(警告信息是我们输出的)
11:42:47: Executing task 'compileReleaseJavaWithJavac'...
Executing tasks: [compileReleaseJavaWithJavac]
Task :annotation:compileJava
...
警告: MyProcessor >>> Myprocessor init
警告: MyProcessor >>> Myprocessor process
警告: MyProcessor >>> 元素名称>>>:MainActivity 是一个类 >> 包名信息是>>>:com.anonyper.annotationapplication.MainActivity
该类下面有如下元素:CONSTRUCTOR>>><init> FIELD>>>name METHOD>>>onCreate METHOD>>>mainMethon
元素名称>>>:name 是一个变量 >> 值为 MainActivity >> name
元素名称>>>:mainMethon 是一个方法 >> 该方法下面有如下参数:
name 类型 >> java.lang.String
该方法返回值:>>java.lang.String
警告: MyProcessor >>> Myprocessor process
警告: MyProcessor >>>
警告: MyProcessor >>> Myprocessor process
警告: MyProcessor >>>
7 个警告Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/4.10.1/userguide/command_line_interface.html#sec:command_line_warningsBUILD SUCCESSFUL in 2s
17 actionable tasks: 16 executed, 1 up-to-date
11:42:50: Task execution finished 'compileReleaseJavaWithJavac'.
- 查看生成的代码
app — > build — > generated — > source — > apt — > release/debug — > com.anonyper.autocreate.AutoCreateClass
package com.anonyper.autocreate;
import java.lang.String;
public final class AutoCreateClass {
public String getMessage() {
return "元素名称>>>:MainActivity 是一个类 >> 包名信息是>>>:com.anonyper.annotationapplication.MainActivity\n"
+ "该类下面有如下元素:CONSTRUCTOR>>><init> FIELD>>>name METHOD>>>onCreate METHOD>>>mainMethon \n"
+ "元素名称>>>:name 是一个变量 >> 值为 MainActivity >> name\n"
+ "元素名称>>>:mainMethon 是一个方法 >> 该方法下面有如下参数:\n"
+ "name 类型 >> java.lang.String\n"
+ "该方法返回值:>>java.lang.String";
}
}
以上,代码编译完就可以在项目中引用了(debug编译之后才可以用)。
AutoCreateClass autoCreateClass = new AutoCreateClass();
autoCreateClass.getMessage()
知识点分解
按照上面的步骤来梳理一下知识点:
创建Java Lib的annotation
我们的注解不仅在app中用到,也需要在注解处理器中用到,同时又只是基本的java类,所以用Java Lib。
TestAnnotation注解类的@Target
//@Target(ElementType.TYPE_USE)//API 26上可以使用
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
之前说过,ElementType.TYPE_USE可以用在任何地方,所以上述两个写法都可以。
用@Target(ElementType.TYPE_USE)的时候,IDE会提示加上版本(@TargetApi)的指定,
image-20190605152630179.png加不加都可以运行的,加的话会发现@RequiresApi、android.os.Build找不到,是因为这是一个Java Lib哦,如有强迫症,可以改为Android Lib。
创建Java Lib的annotationprocessor(注解处理器)
1、只有Java Lib中才会导入AbstractProcessor相关的架包(javax包下),其他类型的lib不会导入AbstractProcessor相关的架包,也就无法自定义继承AbstractProcessor。
2、和annotation分两个lib是因为app在引用的时候写法不一样(一个引用代码,一个只是编译声明):
implementation project(':annotationlib')//引用lib
annotationProcessor project(':testcompiler')//声明注解处理器
所以需要分成两个lib。
注解处理器的编写
1、需要继承AbstractProcessor类,实现process方法。
2、自定义的注解处理器需要加上相关元注解
@SupportedAnnotationTypes("com.anonyper.annotation.TestAnnotation")//注解解释器支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8)//注解解释器支持的版本
@AutoService(Processor.class)//自动生成注解配置信息
public class MyProcessor extends AbstractProcessor {
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取带有TestAnnotation注解信息的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment
.getElementsAnnotatedWith(TestAnnotation.class);
...
return false;//返回false 则意味着其他注解处理器可以继续处理该注解 true即该注解处理器已经处理了,其他的注解处理器就不用处理
}
}
-
SupportedAnnotationTypes("com.anonyper.annotation.TestAnnotation")
@Documented @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface SupportedAnnotationTypes { String[] value(); }
注解处理器支持的注解类型,参数为字符串,需要添加全路径。不加的话在编译时会提示:
No SupportedAnnotationTypes annotation found on com.anonyper.annotationprocessor.MyProcessor, returning an empty set.
然后process不会执行。写错不会提示警告,只是process方法不会执行。
AndroidStudio中 选中文件 — > 右键 — > Copy Reference 可以复制类的全路径
- @SupportedSourceVersion(SourceVersion.RELEASE_8)
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SupportedSourceVersion {
SourceVersion value();
}
注解处理器支持的版本,建议SourceVersion.RELEASE_7及以上。
不写默认为RELEASE_6,我们如果在build.gradle中声明java版本为1.8的话,编译会提示:
警告: No SupportedSourceVersion annotation found on com.anonyper.annotationprocessor.MyProcessor, returning RELEASE_6.
警告: 来自注释处理程序 'org.gradle.api.internal.tasks.compile.processing.NonIncrementalProcessor' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.8'
同时process方法不会被执行。
- @AutoService(Processor.class)
MyProcessor类写好之后,即使app中配置了annotationProcessor project(':testcompiler'),也会报错,因为需要对Javac声明该注解处理器。这里我们用AutoService,它是是谷歌开发的一个会自动在META-INF文件夹下生成Processor配置信息文件的lib,这个配置信息就是告诉编译器我们自定义的注解处理器的信息。使用时需要在build.gradle中配置:implementation 'com.google.auto.service:auto-service:1.0-rc2'
不用AutoService,就需要在testcompiler的main下面创建一个声明文件:
1、在testcompiler的main目录下创建resources文件夹
2、在resources文件夹下创建META-INF文件夹
3、在resources/META-INF文件夹下创建services文件夹
4、在resources/META-INF/service文件夹下创建javax.annotation.processing.Processor文件(这是一个文件)
5、在javax.annotation.processing.Processor文件中写入com.anonyper.annotationprocessor.MyProcessor(我们写的注解类的全路径)
image-20190605151837719.png重新clean一下项目,rebuild一下工程就可以看到
image-20190605152217619.png生成了配置信息。再执行complieDebugJavaWithJavac就可以了。
MyProcessor类分析
MyProcessor继承了AbstractProcessor,主要实现了两个方法:
- void init(ProcessingEnvironment processingEnvironment)
init方法是在加载该注解处理器的时候运行,只会运行一次。
ProcessingEnvironment具体源码不细说,该对象可以返回
Filer filerUtils = processingEnv.getFiler();
Elements elementUtils = processingEnv.getElementUtils();
Messager messagerUtils = processingEnv.getMessager();
Filer:用来创建新源、类或辅助文件的 Filer
Elements:在元素上进行操作的某些实用工具方法的实现,在操作元素是会用到。
Messager:报告错误、警报和其他通知的 Messager,方便编译的时候根据输出信息查找问题。
- booelan process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
该方法会被执行多次,除第一次可以获取注解元素外,其他没有再获取到注解元素(推测后面执行的时候是为了处理通过注解处理器生成的代码),该方法会处理先前 round 产生的类型元素上的注释类型集,并返回这些注释是否由此 Processor 声明。具体的代码逻辑不重复介绍,代码注释都写好了。
关于不同类型元素的说明,请看关于Element的介绍。
代码中使用了javapoet这个三方库的文件,这个是一个辅助生成类、方法等源文件的三方库,使用的时候需要在build.gradle中配置:implementation 'com.squareup:javapoet:1.7.0'//引入生成代码的库
complieDebugJavaWithJavac/complieReleaseJavaWithJavac
每次执行这个命令时,如果文件没有改变过,不会再执行MyProcessor。
debug调试注解处理器
Edit Configurations — > +号 — > Remote — >端口默认5005 /module选择app — > Apply — > OK
![image-20190605161219444.png](https://img.haomeiwen.com/i5207488/a8eb34cf968e1b4d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)项目的gradle.properties中添加:
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005//端口号和配置一样
然后点击debug按钮
打好断点,切换到complieDebugJavaWithJavac,再点击执行。
如果提示
Unable to open debugger port (localhost:5005): java.net.ConnectException "Connection refused: connect"
1、切换端口号,比如5008
2、干掉已经被占用的5005端口应用
然后重新debug。
两个注解处理器处理同一个注解
在annotationprocessor下复制一份MyProcessor,改名为SecondProcessor,其他不变
@SupportedAnnotationTypes("com.anonyper.annotation.TestAnnotation")//注解解释器支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8)//注解解释器支持的版本
@AutoService(Processor.class)//自动生成注解配置信息
public class SecondProcessor extends AbstractProcessor {
...
}
执行结果:如果第一个被执行的注解处理器process返回了fasle,那么第二个也会处理TestAnnotation注解,如果返回了true,则第二个不会处理注解(获取不到对应注解的信息了)
同一个注解,多个文件使用
在app下,复制MainActivity,改名为SecondActivity,其他不变
@TestAnnotation
public class SecondActivity extends AppCompatActivity {
...
}
执行结果:在同一个注解处理器中,调用process方法时,返回了关于该注解所有的元素。
对于变量的注解使用
变量只有是final类型时,才会获取到变量的值,其他都是返回默认值。
其他说明
通过demo和知识梳理,我们对于运行时注解有了了解,知道怎么用,但实际上在使用过程中肯定不会如我们demo所写的只是为了创建做一个类和方法,我们在Android项目中用到的框架使用了注解处理器的有:dagger、butterknife等。butterknife帮助我们生成findviewbyId这样的代码,dagger帮助我们来自动生成实例化的代码。这里面有一个思想叫做IOC(控制反转)的编程思想,dagger、butterknife也是在这种思想下产生的,感兴趣的可以在深入查阅一下资料。
本章就梳理到这里,后续有时间再写一篇关于butterknife和dagger源码的梳理文章,下一章,我们写一写运行时注解的使用知识。
以上均是我结合自己知识+网络查阅,如有理解错误,烦请指出。