Android进阶之路Android开发经验谈Android开发

Java注解知识梳理—自定义注解处理器

2019-06-05  本文已影响12人  AnonyPer

Java注解知识梳理—自定义注解处理器

前言

前面介绍了如何自定义注解以及Java中关于注解的一些元素作用,学会了如何创建注解,接下来就需要了解如何使用注解。

一个注解的@Retention属性说明了该注解使用的生命周期范围,我们也知道一共有三个属性值:

根据注解的声明周期以及java运行的流程节点【java文件 >>〉(javac)>>〉class文件 >>〉(jvm)>>〉执行】我们可以分析出注解的用途会在:javac编译的时候以及jvm运行的时候,其中编译SOURCE、CLASS常用于javac编译阶段RUNTIME常用于jvm运行阶段,本章我们来介绍如何在编译阶段使用注解。

分析

想在编译阶段使用注解,两个前提条件需要满足:

第一个就是创建注解、标注注解,前一章我们已经讲过了,不多说。

第二个需要一个注解处理器(Annotation Processor ,注解处理器是javac的一个工具,存在于Java 5,但是主要API在java 6才加入,主要用来在编译时扫描和处理注解信息)。通过一些配置,这些代码可以在javac编译的时候运行,可以过滤查询出对应的注解信息,然后做一些逻辑处理。

创建demo

经过分析,我们需要掌握的知识就是注解处理器的使用,我们先按照步骤做出一个demo,看一下成果,有一点成就感,然后再逐步梳理对应的知识点。

环境:

目标:

public class AutoCreateClass {
    public String getMessage() {
        return "这里面放一些解析出来的信息";
    }
}

跟这我左手右手一步一步写代码

Android Studio —> File —> New —> New Project—> Empty Activity —> 起名AnnotationApplication

image-20190604182815782.png

Android Studio —> File —> New —> New Module—> Java lib—> 起名annotation,并创建TestAnnotation注解类

image-20190604183911476.png image-20190604185145283.png

TestAnnotation注解类:

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下的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下的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"
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_warnings

BUILD 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即该注解处理器已经处理了,其他的注解处理器就不用处理
  }
}

注解处理器支持的注解类型,参数为字符串,需要添加全路径。不加的话在编译时会提示:

No SupportedAnnotationTypes annotation found on com.anonyper.annotationprocessor.MyProcessor, returning an empty set.

然后process不会执行。写错不会提示警告,只是process方法不会执行。

AndroidStudio中 选中文件 — > 右键 — > Copy Reference 可以复制类的全路径

@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方法不会被执行

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,主要实现了两个方法:

init方法是在加载该注解处理器的时候运行,只会运行一次。

ProcessingEnvironment具体源码不细说,该对象可以返回

Filer filerUtils = processingEnv.getFiler();
Elements elementUtils = processingEnv.getElementUtils();
Messager messagerUtils = processingEnv.getMessager();

Filer:用来创建新源、类或辅助文件的 Filer

Elements:在元素上进行操作的某些实用工具方法的实现,在操作元素是会用到。

Messager:报告错误、警报和其他通知的 Messager,方便编译的时候根据输出信息查找问题。

该方法会被执行多次,除第一次可以获取注解元素外,其他没有再获取到注解元素(推测后面执行的时候是为了处理通过注解处理器生成的代码),该方法会处理先前 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源码的梳理文章,下一章,我们写一写运行时注解的使用知识。

demo源代码

以上均是我结合自己知识+网络查阅,如有理解错误,烦请指出。

上一篇下一篇

猜你喜欢

热点阅读