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来构造源文件