注解

98 阅读3分钟
注解:注解单独存在的时候是没有任何意义的,只是一个标记、标签。

一、注解的了解

注解是一个标签,对打标签的参数进行语法检查。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AnnotationTest {
//    String value() default "xxx";  //如果有默认值,注解可以不要
    String value();//没有默认值,注解必须传值
    String id();
}

  • 注解用@interface
  • Target 是定义注解可以注解在什么地方 ,Target是一对多的关系,一个注解可以注解在多个地方例如:@Target({ElementType.TYPE,ElementType.FIELD})。如果没有Target,表示可以注解在任何位置,类、方法、变量、参数...
  • Retention是保留级别,保留注解到什么时候:
    1. SOURCE阶段:保留在源码阶段,并被编译器忽略,经过javac的编译,编译成class之后就被抹除掉了;
    2. CLASS阶段:保留在class文件,会在jvm加载文件的时候忽略这个注解;
    3. RUNTIME阶段:是运行时利用反射获取注解。由jvm保留,因此运行环境可以使用。 SOURCE < ClASS < RUNTIME,CLASS包含SOURCE,RUNTIME包含SOURCE、CLASS
@AnnotationTest(value = "v1",id="test")//如果是有value和id等多个参数,则必须都以key、value的形式
//@AnnotationTest(id="test")//如若是其他的参数名字比如是id,则就必须传上
//@AnnotationTest("test")//value是特殊的参数,可以不用key,直接传值
//@AnnotationTest
public class TestJava {}

二、注解的应用场景

由注解的三个不同保留级别可知,注解作用于源码、字节码、运行时
  1. 源码阶段(SOURCE):APT(注解处理器),在编译期能够获取注解与注解声明的类,包括类中所有成员信息,一般用于生成额外的辅助类,例如ARouter、ButterKnife保留在class级别;IDE语法检查,例如IntDef(ANNOTATION_TYPE 元注解,可以注解在注解上的注解)。
  2. 字节码阶段(CLASS):字节码增强(在字节码中写代码),在编译出class后,通过修改class数据以实现修改代码逻辑目的,对于是否需要修改的区分或者修改为不同的逻辑的判断可以使用注解,字节码增强例子:字节码插桩、热修复;
  3. 运行时阶段(RUNTIME):反射,在程序运行期间,通过反射技术动态获取注解与其元素,而完成不同的逻辑判定;
创建注解处理器
  1. 一定要创建java模块,因为注解处理器运行在编译阶段,java虚拟机不认识.java文件,需要由javac编译成.class文件,解析java类就需要采集类所有的注解信息,然后把注解信息包装成节(Element),再由javac调起注解处理程序。注解处理程序不需要我们手动调用,由javac调用。在生成.class之前执行注解处理程序。

image.png

plugins {
    id 'java-library'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}
  1. 创建类继承AbstractProcessor,注解处理程序,由javac进行实例化,调用process,AbstractProcessor由jdk提供,如果要使用注解处理器,必须要进行注册。
  2. 注册,在新建的java model的main下面创建recourses文件夹,然后创建META-INF.services文件夹,最后创建javax.annotation.processing.Processor文件,在文件里写入MyBaseProcessor全路径com.example.compilerjava.MyBaseProcessor。

image.png

@SupportedAnnotationTypes("com.example.kotlindemo.AnnotationTest")//只关心的注解,只关心的注解的全类名输上
public class MyBaseProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager();//输出日志
        messager.printMessage(Diagnostic.Kind.NOTE,">>>>>>>>>>>>>");
        return false;
    }
}
  1. 在app的build.gradle里引入注解处理器
   dependencies {
    implementation fileTree(dir: "libs", include: ['*.aar', "*.jar"])//注意要有这个
        annotationProcessor project(':compilerJava')

}

注解处理器由Javac帮我们调起,javac先调用注解处理程序,然后才会把java文件编译成.class文件。

三、使用

public static void main(String[] args) {
        setDrawable(1);//报错
        setDrawable(R.drawable.ic_launcher_background);
    }
    
    public static void setDrawable(@DrawableRes int id){
        
    }

@DrawableRes、@IntDef 语法检查的注解,枚举里面每一个元素其实是一个对象,一个对象光对象头就占12个字节,枚举比较占内存,所以一般使用常量。一个对象由12个字节的对象头+成员,还要进行8字节对齐。

 public static void main(String[] args) {
       setCurrentDay(WeekDay.MONDAY);
    }

    private static WeekDay mCurrentDay;

    enum WeekDay{
        SUNDAY,MONDAY
    }

    public static void setCurrentDay(WeekDay currentDay) {
        mCurrentDay = currentDay;
    }

 public static void main(String[] args) {
        setCurrentDay(11);//报错
        setCurrentDay(SUNDAY);
    }

    private static final int SUNDAY = 1;
    private static final int MONDAY = 2;
    @Week private static int currentDay;//添加注解


    @IntDef({SUNDAY,MONDAY})
    @Target({ElementType.FIELD,ElementType.PARAMETER})//指定为变量和方法参数
    @Retention(RetentionPolicy.SOURCE)
    @interface Week{

    }


    public static void setCurrentDay(@Week int current) {//添加注解
       currentDay = current;
    }

语法检查:是由IDE实现,IDE插件实现的,AandroidStudio是由java实现的。

  • javac 编译
  • javap 反编译

四、反射

反射都是基于class的

 static class Response<T>{
        T data;
        int code;
        String message;

        @Override
        public String toString() {
            return "Response{" +
                    "data=" + data +
                    ", code=" + code +
                    ", message='" + message + '\'' +
                    '}';
        }


        public Response(T data, int code, String message) {
            this.data = data;
            this.code = code;
            this.message = message;
        }

    }


    static class Data{
        String result;

        public Data(String result) {
            this.result = result;
        }

        public void setResult(String result) {
            this.result = result;
        }

        @Override
        public String toString() {
            return "Data{" +
                    "result='" + result + '\'' +
                    '}';
        }
    }

    static class TypeRefrence<T>{
        Type type;
        protected TypeRefrence() {//如果是在不同的包名下,protected是受保护的,在调用是必须要加花括号
            //获得泛型类型
            Type genericSuperclass = getClass().getGenericSuperclass();
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            Type [] actualTypeArguments = parameterizedType.getActualTypeArguments();//因为不止有一个泛型,可以有多个泛型,所以返回数组
            type = actualTypeArguments[0];
        }


        public Type getType() {
            return type;
        }
    }



    public static void main(String[] args) {
        Response<Data> dataResponse = new Response<>(new Data("s数据"),1,"成功");
        //序列化
        Gson gson = new Gson();
        String json = gson.toJson(dataResponse);
        System.out.println(json);

        //反序列化
        Type type = new TypeToken<Response<Data>>() {
        }.getType();
        Response<Data> response = gson.fromJson(json,type);//如果Response不是泛型类,可以直接写成Response.class,如果是泛型类
        System.out.println(response.data.getClass());

        //有花括号代表是匿名内部类;没有花括号代表是对象,是TypeRefrence对象,TypeRefrence对象没有Response<Data>
        //或者也可以将改成抽象类,static abstract class TypeRefrence<T> 调用的时候没有花括号就报错
        Type customerType = new TypeRefrence<Response<Data>>(){}.getType();
        Response<Data> resp= gson.fromJson(json,customerType);
        System.out.println(">>>"+resp.data.getClass());


    }