Android APT注解扫盲

前言

刚接触java不久的小猿们可能不知道注解这东东干嘛的,或者有些老鸟们也是一知半解只会用而不知其所以然。所以,在了解注解是什么之前,我先来说说这个东东有神马用,场景都有哪些,目前有没有范本。知道这些后,再看看,各位当前阶段是不是需要学,还是后续用到在深究。首先,说下用处,通过注解主要可以简化开发人员重复性工作和自动化过程,但不提高代码执行效率,用于框架的搭建与工具的开发;大部分应用场景包含在于编译阶段与运行时阶段自动生成代码;目前,用注解开发的框架有很多如:ButterKnife, EventBus, Retrofit,ARouter等。嗯,闲扯了这么多,现在说下读完这篇文章你能知道些啥,先上个目录

  • 注解的概念
  • 元注解
  • 自定义注解(注解的使用)
    • 运行时注解(引擎 --> 反射)
    • 编译时注解(核心 --> 注解解析器)
  • 自动生成代码帮助库JavaPoet

什么是注解

注解是代码里的特殊标记,这些标记可以在编译类加载运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署

元注解

元注解就是注解的注解,哭,说啥勒。其实可以理解为类比于java提供的关键字,元注解目前分为4种如下(类比于java提供多个关键字)。每个元注解(关键字)怎么起作用的,那就是编译器的事咯。

1.@Retention:定义该注解的生命周期(被描述的注解在什么范围内有效)。参数是RetentionPolicy枚举对象

@Retention-RetentionPolicy类型 说明
RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,用于编译时注解
RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在(用于运行时注解

2.@Target:表明我们注解可以出现的地方(没有说明包含所有),参数是一个ElementType类型的数组

@Target-ElementType类型 说明
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE

3.@Document(用的少)

表明我们标记的注解可以被javadoc此类的工具文档化

4.@Inherited(用的少)

表明我们标记的注解是被继承的,默认是false。比如,如果一个父类使用了@Inherited修饰的注解,则允许子类继承该父类的注解。

自定义注解

自定义注解流程,so easy 如下:

  1. 申明注解
  2. 解析注解(两种模式如下)
    • 反射方式的注解处理器(运行时注解
    • AbstractProcessor 的实现类注解处理器(编译时注解
  3. 配置注解解析器
  4. 注解的使用

下面就通过AS来演示这几步骤,首先,创建Module1:annotation(选择为 Java library),该Module用于申明注解,比如:(ARouter用于模拟阿里的路由框架、Bindview用于模拟Butterknife 库)

/**
 * 根据ID查看View的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FindViewById {
    // 使用value命名,则使用的时候可以忽略,否则使用时就得把参数名加上
    int value();
}
/**
 * 设置点击事件
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetOnClickListener {
    int id();
    String methodName();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
    String path();
    String group() default "";
}

步骤二:创建Module2:compiler(选择为 Java Library或者Android Library),该Module用于解释注解

注意:一般情况运行时注解选择Android Library,编译时注解使用Java Library(因为AbstractProcessor类依赖于rt.jar包)

反射方式的注解处理器(运行时注解)

import android.app.Activity;
import android.view.View;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Annotation解析器
 */
public class ButterKnifeProcess{
    /**
     * 解析
     * @param target 解析目标
     */
    public static void parse(final Activity target) {
        try {
            Class<?> clazz = target.getClass();
            // 获取所有的字段
            Field[] fields = clazz.getDeclaredFields(); 
            FindViewById byId;
            SetOnClickListener clkListener;
            View view;
            String name;
            String methodName;
            int id;
            for (Field field : fields){
                //返回该元素上存在的所有注解
                Annotation[] annotations = field.getAnnotations();
                for(Annotation annotation:annotations) {
                    if (annotation instanceof FindViewById) {
                        // 获取FindViewById对象(返回该元素上存在的指定类型的注解)
                        byId = field.getAnnotation(FindViewById.class);
                        // 反射访问私有成员,必须进行此操作
                        field.setAccessible(true); 
                        //字段名
                        name = field.getName(); 
                        id = byId.value();
                        // 查找对象
                        view = target.findViewById(id);
                        if (view != null)
                            field.set(target, view);
                        else
                            throw new Exception("Cannot find.View name is " + name + ".");
                    } else if (annotation instanceof SetOnClickListener) { // 设置点击方法
                        //返回该元素上存在的指定类型的注解
                        clkListener = field.getAnnotation(SetOnClickListener.class);
                        field.setAccessible(true);
                        // 获取变量
                        id = clkListener.id();
                        methodName = clkListener.methodName();
                        name = field.getName();
                        view = (View) field.get(target);
                        if (view == null) { // 如果对象为空,则重新查找对象
                            view = target.findViewById(id);
                            if (view != null)
                                field.set(target, view);
                            else
                                throw new Exception("Cannot find.View name is " + name + ".");
                        }
                        // 返回当前类所有的方法,即包括public、private和protected,不包括父类
                        Method[] methods = clazz.getDeclaredMethods();
                        boolean isFind = false;
                        for (final Method method:methods) {
                            if (method.getName().equals(methodName)) {
                                isFind = true;
                                view.setOnClickListener(new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        try {
                                            method.invoke(target);
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });

                                break;
                            }
                        }

                        // 没有找到
                        if (!isFind) {
                            throw new Exception("Cannot find.Method name is " + methodName + ".");
                        }

                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

AbstractProcessor 的实现类注解处理器(编译时注解)

package com.jsonan.compile;

import com.google.auto.service.AutoService;
import com.jsonan.annotation.ARouter;

import java.io.IOException;
import java.io.Writer;
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.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
 * AbstractProcessor 是一个抽象类,它实现了 Processor 接口。
 * Processor 接口的实现类必须提供一个公共的无参数构造方法,虚拟机工具将使用该构造方法实例化 Processor。
 * 虚拟机工具框架与实现此接口的类交互过程:
 * 1.创建 Processor 对象 
 * 2.调用 Processor 对象 的 init 方法
 * 3.调用 Processor 对象 的 getSupportedAnnotationTypes、getSupportedOptions 和 getSupportedSourceVersion 方法(下面我们使用注解的方式实现这几个方法)
 * 4.调用 Processor 对象 的调用 process 方法
 */
@AutoService(Processor.class)
// 设置此注解解析器支持的注解类型的名称
@SupportedAnnotationTypes("com.jsonan.annotation.ARouter")
// 设置此注解解析器支持的最新的 JDK 版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 设置此注解解析器识别的选项
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {
	//一个用来处理Element元素的工具类
    private Elements elementUtils;
    //用来处理TypeMirror的工具类
    private Types typeUtils;
    //输出警告、错误日志工具类
    private Messager messager;
    //文件生成器
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        //该content内容的参数从使用注解的Module中的build.gradle里传递,如:
        //android {
		//    defaultConfig {
        //        javaCompileOptions{
        //            annotationProcessorOptions {
        //                arguments = [content:'hello apt']
        //            }
        //        }
		//    }
        String content = processingEnvironment.getOptions().get("content");
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }
	/**
     *注解Processor扫描出的结果会存储进roundEnvironment中,可以在这里获取到注解内容,编写你的操作逻辑
     *注意:process()函数中不能直接进行异常抛出,否则程序会异常崩溃
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            return false;
        }
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        for (Element element : elements) {
            String packName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
            String finalClassName = className + "$$ARouter";
            try {
                				                             //此时会在app\build\generated\ap_generated_sources\debug\xxx\包下生成MainActivity$$ARouter.java文件
                JavaFileObject sourceFile = filer.createSourceFile(packName + "." + finalClassName);
                Writer writer = sourceFile.openWriter();
                writer.write("package " + packName + ";\n");
                writer.write("public class " + finalClassName + "{\n");
                writer.write("public static Class<?> findTargetClass(String path){\n");
                // 返回此元素上存在的指定类型的注解
                ARouter aRouter = element.getAnnotation(ARouter.class);
                writer.write("if (path.equalsIgnoreCase(\"" + aRouter.path() + "\")){\n");
                writer.write("return " + className + ".class;\n}\n");
                writer.write("return null;\n");
                writer.write("}\n}\n");
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        return false;
    }
}

注解处理器的核心是process()方法,而process()方法的核心是Element元素。Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的Element。它可以代表包、类、接口、方法、字段等多种元素种类。所有Element肯定是有好几个子类。如下所示。

Element子类 解释
TypeElement 类或接口元素
VariableElement 字段、enum常量、方法或构造方法参数、局部变量或异常参数元素
ExecutableElement 类或接口的方法、构造方法,或者注解类型元素
PackageElement 包元素
TypeParameterElement 类、接口、方法或构造方法元素的泛型参数

关于Element类里面的方法我们也做一个简单的介绍:

    /**
     * 返回此元素定义的类型,int,long这些
     */
    TypeMirror asType();

    /**
     * 返回此元素的种类:包、类、接口、方法、字段
     */
    ElementKind getKind();

    /**
     * 返回此元素的修饰符:public、private、protected
     */
    Set<Modifier> getModifiers();

    /**
     * 返回此元素的简单名称(类名)
     */
    Name getSimpleName();

    /**
     * 返回封装此元素的最里层元素。
     * 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素;
     * 如果此元素是顶层类型,则返回它的包;
     * 如果此元素是一个包,则返回 null;
     * 如果此元素是一个泛型参数,则返回 null.
     */
    Element getEnclosingElement();

    /**
     * 返回此元素直接封装的子元素
     */
    List<? extends Element> getEnclosedElements();

    /**
     * 返回直接存在于此元素上的注解
     * 要获得继承的注解,可使用 getAllAnnotationMirrors
     */
    List<? extends AnnotationMirror> getAnnotationMirrors();

    /**
     * 返回此元素上存在的指定类型的注解
     */
    <A extends Annotation> A getAnnotation(Class<A> var1);

Element的子类的使用举例,如下:

//VariableElement 修饰属性、类成员的注解
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
    //ElementType.FIELD注解可以直接强转VariableElement
    VariableElement variableElement = (VariableElement) element;
	//getEnclosingElement 属性、类成员的直接封装上层元素就是类,所以是TypeElement
    TypeElement classElement = (TypeElement) element.getEnclosingElement();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //类成员名
    String variableName = variableElement.getSimpleName().toString();

    //类成员类型
    TypeMirror typeMirror = variableElement.asType();
    String type = typeMirror.toString();
}

注意:上面process()函数中我们使用JavaFileObject的方式生成java文件,如果java文件比较多,内容复杂时很容易出错,有个便捷的工具库供我们使用JavaPoet,这个下面最后部分详说,暂且跳过先把整个流程说完

步骤三:

方式一:创建 javax.annotation.processing.Processor 文件:在 main 文件夹下,与 java 同一级目录中创建 resources文件夹,再创建 META-INF/services/javax.annotation.processing.Processor 文件,在javax.annotation.processing.Processor 文件中指明注解解析器类 Processor 的 类路径(处理器的全类名),如果有多个注解处理器换行写入就可以。

方式二:

  1. 在处理器Module:compile的build.gradle中添加google的自动生成服务库,如:

    dependencies {
        implementation 'com.google.auto.service:auto-service:1.0-rc4'
        annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
        implementation project(path:':annotation')
    }
    sourceCompatibility = "7"
    targetCompatibility = "7"
    
  2. 在注解处理器类前面加上@AutoService(Processor.class),如:

    @AutoService(Processor.class)
    public class ButterKnifeProcess{
    	...
    }
    

方法三:如果第二步创建的Module是Android Library ,此时可以在build.gradle 文件的 dependencies 块中,添加 support-annotations 依赖项

dependencies { compile 'com.android.support:support-annotations:24.2.0' } 

若该模块使用了appcompat库,则无需添加support-annotations依赖项。因为 appcompat库已经依赖注解库

步骤四:注解的使用,需要在使用它 Module 的 build.gradle 文件中添加依赖。比如在项目主 Module :app要使用,则在主 Module的 build.gradle 中添加依赖:

dependencies {
    annotationProcessor project(path: ':compile(解析注解Module的名字)')
    implementation project(path: ':annotation(申明注解Module的名字)')
}

如果编写的注解解析 Module 只是在编译时扫描,还可以将依赖修改为:

dependencies {
    annotationProcessor project(path: ':compile(解析注解Module的名字)')
    complileOnly project(path: ':annotation(申明注解Module的名字)')
}

编译时注解、运行时注解使用如下:

//运行时注解
@ARouter(path = "/app/MainActivity", group = "app")
public class MainActivity extends AppCompatActivity {
	
    //自动绑定view(编译时注解)
    @FindViewById(R.id.tv_Toast)
    @SetOnClickListener(id = R.id.tv_Toast, methodName = "click")
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnifeProcess.parse(this);
    }
    /**
     * 点击事件
     */
    public void click() {
        Toast.makeText(this, "Hello compile annotatioin", Toast.LENGTH_SHORT).show();
    }
}

JavaPoet的简单介绍与使用

JavaPoet是一款使用编程的方式自动生成java文件的三方库,通常用编写模板类,框架等

直接上使用(对标上面使用Filer方式编译时生成的MainActivity$$ARouter.java文件),JavaPoet具体介绍参考下面的参考链接

使用JavaPoet需要生成的文件

package com.jsonan.annotationlearning;

import java.lang.Class;
import java.lang.String;

public class MainActivity$$ARouter {
    public static Class<?> findTargetClass(String path) {
        if (path.equalsIgnoreCase("/app/MainActivity")) {
            return MainActivity.class;
        }
        return null;
    }
}

JavaPoet使用如下:

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    ClassName path = ClassName.get("java.lang", "String");
    ParameterSpec savedInstanceState = ParameterSpec.builder(path, "path").build();
    //构造一个方法
    MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")      //名称
               .addModifiers(Modifier.PUBLIC, Modifier.STATIC)         //修饰
               .returns(Class.class)                                   //返回
               .addParameter(savedInstanceState)                       //参数
        .beginControlFlow("if($N.equalsIgnoreCase(\"/app/MainActivity\"))",savedInstanceState)
               .addStatement("return MainActivity.class")  //语句
               .endControlFlow()
               .addStatement("return null")  //语句
               .build();
    //构造一个类
    TypeSpec MainActivity$$ARouter = TypeSpec.classBuilder("MainActivity$$ARouter")         		  //名称
             .addModifiers(Modifier.PUBLIC)                         //修饰
             .addMethod(findTargetClass)                            //方法
                        .build();
    JavaFile javaFile = JavaFile.builder("com.jsonan.annotationlearning",MainActivity$$ARouter).build();
        try {
                javaFile.writeTo(System.out);
            } catch (IOException e) {
                e.printStackTrace();
            }
        File file = new File("."+"\\src\\");
        if (file.exists()) {
            file.delete();
        }
        javaFile.writeTo(file);
        return false;
    }

参考链接

https://blog.csdn.net/wuyuxing24/article/details/81139846

https://blog.csdn.net/wangjiang_qianmo/article/details/99350360

Element子类介绍使用

JavaPoet 入门的介绍与使用

javapoet官网

上一篇:Modelica建模方式2--基于组件建模和基于子系统建模


下一篇:java汉字转拼音和获取汉语拼音首字母