Android AOP之 APT

934 阅读8分钟

AOP

面向切向编程(Aspect Oriented Programming),相对于面向对象编程,AOP和OOP一样,是一种程序设计思想,而非技术手段。

AOP技术实现的方式

  • APT
  • AspectJ(编译时候)
  • Transform + Javassist/ASM
  • Dexposed,Xposed等(运行时)

在AOP之前需要了解一些关于注解的相关的知识。AOP和注解紧密相关

注解技术相关知识

注解的声明

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE})
public @interface HelloAnnotation {
}

定义注解时需要一些元注解,如@Retention和@Target现在我们一个一个的解释元注解

@Documented 元注解 该注解表示,是否将注解包含在JavaDoc中,在生成 Doc文档的时候会显示
@Retention元注解 该注解表该注解在什么级别下被保存,可选的RetentionPolicy参数分为三种:

  • SOURCE:该类型的注解只会保留在源码里,经过编译器编译后,生成的class文件里是不会存在该注解信息的。
  • CLASS:注解在class文件中可用,但是会被VM丢弃(该类型的注解信息会被保留在源码与class文件中,在执行的时候,不会被加载到虚拟机中)。注意:当注解未定义Retention值时,默认值是CLASS级别
  • RUNTIME:VM将在运行期间也保留注解,因此可以通过反射机制读取到注解的信息(该类型的注解信息会被保留在源码、class文件和虚拟机执行期间)

@Target元注解 该注解主要表示该注解可以用于什么地方,其中可能ElementType参数为以下几种情况

  • TYPE:用于类、接口(包括注解类型)或enum声明
  • FIELD:用于字段声明,包括enum实例
  • METHOD:用于方法声明
  • PARAMETER:用于参数声明
  • CONSTRUCTOR:用于构造函数声明
  • LOCAL_VARIABLE:用于局部变量声明
  • ANNOTATION_TYPE:用于注解可也用于注解声明(应用于另一个注解上)
  • PACKAGE:用于包声明
  • TYPE_PARAMETER:用于类上泛型参数的说明(JDK1.8加入)
  • TYPE_USE:用于任何类型的声明(JDK1.8加入)

@Inherited元注解 该注解表示,允许子类继承父类中的注解

@Repeatable元注解(JDK 1.8之后新加入的) 该注解是JDK 1.8新加入的,该注解表示,可以在同一个地方多次使用同一种注解类型。在JDK 1.8之前是无法在同一个类型上使用相同的注解的。

注解的使用与支持属性类型

注解支持属性类型:

  • 所有的基本类型(int、float、boolean)等
  • String
  • Class
  • enum
  • Annotaion
  • 以及以上类型的数组 注意!!!!也不能使用任何类型的包装类型 以下是使用
//声明枚举
enum Week {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String text();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface WorldAnnotation {
    //基本类型及其数组类型
    int intAttr();
    float floatAttr() ;
    boolean booleanAttr() ;

    int[] intArray() ;
    float[] floatArray() ;
    boolean[] booleanArry() ;

    //String类型及其数组类型
    String stringAttr() ;
    String[] stringArray();

    //Class类型及其数组类型
    Class classAttr() ;
    Class[] classArray();

    //enum类型及其数组类型
    Week day() ;
    Week[] week();

}

Java的整个类加载机制

  • 将程序中的*.java文件通过javac命令编译成扩展名为*.class文件。其中*.class文件保存着Java代码转换后的虚拟机指令。

  • 当需要使用某个类时,JVM(Java 虚拟机)将会加载它的*.class文件,并创建对应的Class对象。其中Class对象中不仅有着类的声明定义,还有 Constructor(类的构造器定义)、Field(类的成员变量定义)、Method(类的方法定义)、Package(类的包定义)

注解到底和类的加载

当声明了注解时,将注解的生命周期设置为@Retention(RetentionPolicy.RUNTIME),那么在编译成class文件的时候,会将注解添加到文件中去,那么JVM根据class文件生成相应的Class对象之后就会带有注解信息。那么我们通过Class对象中的Constructor、Field、Method等类,就能获取其上声明的注解信息了。 注解编译后,其实注解最终会继承Annotation接口。也就是说注解最终会以java.lang.annotation.Annotation对象的形式在Class对象中进行展示或存储。

注解的处理

方便处理接口信息以及实现面向对象的规则,其中Constructor、Field、Method、Class、Package类都实现了AnnotatedElement接口,也就是最终的注解处理全部都交给了AnnotatedElement接口来实现。AnnotatedElement接口提供获注解的方法

方法名称返回值方法说明
getAnnotation(Class annotationClass) T返回元素上指定类型的注解,如果无,则返回为null
getAnnotations()Annotation[ ]返回元素上存在的所有注解,包括从父类继承的
getAnnotationsByType(Class annotationClass) since 1.8 T [ ]返回元素上指定的类型的注解数组,包括父类的注解,如果无,返回长度为0的数组,该方法与getAnnotation(Class annotationClass)的主要区别是,该方法可以检查注解是不是重复的。如果是这样,尝试通过“查看”容器注释来找到该类型的一个或多个注释。
getDeclaredAnnotation(Class annotationClass) T返回该元素上的指定类型的所有的注解,不包括父类的注解,如果无,返回长度为0的数组
getDeclaredAnnotationsByType(Class annotationClass) since 1.8 T [ ]同getAnnotationsByType(Class annotationClass)方法类似,只是获取的注解中不包括父类的注解
getDeclaredAnnotations()Annotation[ ]返回该元素上的所有的注解,不包括父类的注解,如果无,返回长度为0的数组

其中getAnnotationsByType(Class annotationClass)与getDeclaredAnnotationsByType(Class annotationClass)方法是jdk 1.8之后提供的接口默认实现方法。需要注意的是该两个方法是支持注解的@Repeatable,而其他方法是不支持的。那么在平时开发中,我们可以根据自己的项目需求选取不同的方法。

APT

APT(Annotation Processing Tool)是一种编译期注解处理技术,可以在代码编译期通过定义注解和处理器来实现编译期生成新的 Java 文件。APT工具不仅能解析注解,还能根据注解生成其他的源文件,最终将生成的新的源文件与原来的源文件共同编译

APT注解技术的组成

 注解处理器(AbstractProcess)+ 代码处理(javaPoet)+ 处理器注册(AutoService)+ apt

APT技术使用规则

APT项目项目构建的一个规则图

APT使用依赖

  • 注解处理器库(包含我们的注解处理器)
  • 注解声明库(用于存储声明的注解)
  • 实际使用APT的Android/Java项目

注解处理工具依赖注解声明库,Android/Java项目同时依赖注解处理工具库与注解声明库。 在Android项目默认是不包含 APT相关类的。所以要使用APT技术,那么就必须创建一个Java Library

注解处理器的声明

每一个注解处理器都需要承AbstractProcessor

class TestProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {}
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }
}
  • init(ProcessingEnvironment processingEnv):每个注解处理器被初始化的时候都会被调用,该方法会被传入ProcessingEnvironment 参数。ProcessingEnvironment 能提供很多有用的工具类,Elements、Types和Filer
  • process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):注解处理器实际处理方法,一般要求子类实现该抽象方法,你可以在在这里写你的扫描与处理注解的代码,以及生成Java文件。其中参数RoundEnvironment ,可以让你查询出包含特定注解的被注解元素,
  • getSupportedAnnotationTypes(): 返回当前注解处理器处理注解的类型,返回值为一个字符串的集合。其中字符串为处理器需要处理的注解的合法全称
  • getSupportedSourceVersion():用来指定你使用的Java版本,通常这里返回SourceVersion.latestSupported()。

注册注解处理器

我们要将注解处理器注册到Java编译器中去,不需要手动 google提供 AutoService,我们只需要在注解器类上声明@AutoService(Processor.class)就可以生成META-INF/services/javax.annotation.processing.Processor文件的

注解处理器的扫描

  • 注解处理过程中,我们需要扫描所有的Java源文件,源代码的每一个部分都是一个特定类型的- -- Element,也就是说Element代表源文件中的元素,例如包、类、字段、方法等。
  • Parameterizable:表示混合类型的元素(不仅只有一种类型的Element)
  • TypeParameterElement:带有泛型参数的类、接口、方法或者构造器。
  • VariableElement:表示字段、常量、方法或构造函数。参数、局部变量、资源变量或异常参数。
  • QualifiedNameable:具有限定名称的元素
  • ExecutableElement:表示类或接口的方法、构造函数或初始化器(静态或实例),包括注释类型元素。
  • TypeElement :表示类和接口
  • PackageElement:表示包

元素种类判断

通过roundEnvironment.getElementsAnnotatedWith(指定类.class)获取源文件中所有包含@Test注解的元素,通过调用element.getKind()具体判断当前元素种类,其中具体元素类型为ElementKind枚举类型

枚举类型种类
PACKAGE
ENUM枚举
CLASS
ANNOTATION_TYPE注解
INTERFACE接口
ENUM_CONSTANT枚举常量
FIELD字段
PARAMETER参数
LOCAL_VARIABLE本地变量
EXCEPTION_PARAMETER异常参数
METHOD方法
CONSTRUCTOR构造函数
OTHER其他

元素类型判断 TypeMirror表示 Java 编程语言中的类型。这些类型包括基本类型、声明类型(类和接口类型)、数组类型、类型变量和 null 类型。还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。我们一般用TypeMirror进行类型判断。

文件生成

JavaPoet来构造源文件