注解声明
java中所有的注解,默认实现Annotation接口,与class相比定义注解需要使用@interface关键字
public @interface CAnnotation{
}
元注解
定义:对注解类型进行注解的注解类(听着有点绕其实就是作用在注解上的注解)称之为meta-annotation(元注解)
一般在使用注解的时候用的两个注解@Taget 和@Retention 另外还有两个@Documented,@Inherited
-
@Taget
注解另一个注解,限定可以应用注解的java元素类型.
*ElementType.ANNOTATION_TYPE 应用与注解类型 限定注解
*ElementType.CONSTRUCTOR 应用与构造函数
*ElementType.FIELD 应用于字段或属性
*ElementType.LOCAL_VARIABLE 应用于局部变量
*ElementType.METHOD 应用于方法
*ElementType.PACKAGE 应用于包声明
*ElementType.PARAMETER 应用于方法参数
*ElementType.TYPE 应用于类的任何元素 -
@Retention
注解制指定注解的存储方式:
*RetentionPolicy.SOURCE 标记的注解仅保留在源码级别中,被编译器忽略
*RetentionPolicy.CLASS 标记的注解在编译时由编译器保留,但是java虚拟机JV会忽略
*RetentionPolicy.RUNTIME 标记的注解由JVM保留,运行时还击可以使用它
@Rentention三个值中SOURCE<CLASS<RUNTIME
eg:
@Target(Element.TYPE,Element.FIELD)//可以指定多个限定类型:允许在类与属性上标记
@Rentention(RententionPolicy.SOURCE)//指定注解保留在源码级
public @interface annotationC{
String value();//无默认值
int age() default 1;//有默认值
}
//使用
@annotationC(value="v",age=0)
class AAA{
}
[注意] 如果定义注解参数时,仅有value参数,那么在使用注解时可以直接写值:@annotationC(“value”)
应用场景
通过以上内容可知Rentention元注解的存储方式有三种,那么逐一看一下:
- SOURCE
它是作用在源码级别,可以提供给IDE语法检查,APT等场景使用(后边在介绍)
eg:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RESOURCE)
public @interface TestAnnotation {
int value();
String id() ;
}
@TestAnnotation(value = 1,id="2")
public class Test {
}
jvm编译字节码显示
public class com/enjoy/annotation/Test {
// compiled from: Test.java
// access flags 0x1
public ()V
L0
LINENUMBER 4 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
RETURN
L1
LOCALVARIABLE this Lcom/enjoy/annotation/Test; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
jvm忽略的注解信息在字节吗中并没有看到相关信息
- 在开发中会应用到IDE 语法检查,suppor-annotations与androidx.annotation中均有提供@IntDef,@StringDef等注解此注解可以起到限定参数的作用
@IntDef(value = {Utils.MAN, Utils.WOMAN})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
public @interface Teacher {
}
public class Test {
public static void main(String[] args) {
//compare(1);//Must be one of: Utils.MAN, Utils.WOMAN
compare(Utils.MAN);
}
public static void compare(@Teacher int value){
//do something
}
}
同样是传递int类型值,加了限定注解后,输入类型必须是指定类型否则报错,android中传递资源文件也是这样着的
APT注解处理器
APT全称"Annotation Processor Tools 意为注解处理器
编写好的java源文件-----javac------>虚拟机能够加载解析的字节吗CLASS文件,注解处理器是javac自带的一个工具,作用是编译时期扫描注解信息,我们注册自己的注解处理器,将注解信息传递给注解处理器处理
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用
框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的
是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
自定义注解处理器
@SupportedAnnotationTypes("com.enjoy.annotation.Teacher")//自己定义注解的全路径
public class TeacherProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "================================");
//可以网络请求
// 反射操作等等
return false;
}
}
- ClASS
定义为class的注解,会保留在class文件中,但是会被虚拟机jvm忽略(无法在运行期间反射获取注解).
此时完全符合此种注解的应用场景是字节码操作,比如AspectJ,热修复Roubust;
字节码操作是指直接修改字节码Class文件以达到修改代码执行逻辑的目的,比如在程序中多处需要进行是否登录判断
如果我们使用普通的编程方式,需要在代码中进行 if-else 的判断,如果判断点非常多,就需要在每个判断点加
入此项判断。此时,我们可以借助AOP(面向切面)编程思想,将程序中所有功能点划分为: 需要登录 与 无需登录
两种类型,即两个切面。对于切面的区分即可采用注解。
//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}
@Login
public void jumpA(){
startActivity(new Intent(this,AActivity.class));
}
public void jumpB(){
startActivity(new Intent(this,BActivity.class));
}
//字节码
public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}
public void jumpB() {
startActivity(new Intent(this,BActivity.class));
}
- RUNTIME
注解保留至运行期,我们能够在运行期间结合反射技术获取注解中的所有信息。