美文网首页
注解及其三类应用场景

注解及其三类应用场景

作者: 秀儿2020 | 来源:发表于2020-07-09 15:30 被阅读0次

    注解(Annotation)

    注解又称为Java标注,是JDK5.0引入的一种注释机制,注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据,注解对他们注解的代码的操作没有直接的影响。注解本身没有意义,他需要结合反射和插桩等技术才有意义

    声明注解
    //使用@interface 关键字声明注解
    public @interface FirstAnnotation{
      
    }
    
    元注解

    能够应用标注在其他注解上的注解我们称之为元注解,在自定义注解时,我们需要使用的元注解有两个,分别是@Target和@Retention,这两个元注解通过传递参数,限制其标记的注解。

    • @Target 标记一个注解的应用范围,即该注解可以应用在哪些元素上

      • 可以应用于注解类型 ElementType.ANNOTATION_TYPE
      • 可以应用于构造函数 ElementType.CONSTRUCTOR
      • 可以应用于字段或属性 ElementType.FIELD
      • 可以应用于局部变量 ElementType.LOCAL_VARIABLE
      • 可以应用于方法 ElementType.METHOD
      • 可以应用于包 ElementType.PACKAGE
      • 可以应用于方法中的参数 ElementType.PARAMETER
      • 可以应用于类的任何元素 ElementType.TYPE
    • @Retention 标记了一个注解的存活时间

      • 标记的注解仅保留在源码级别中,即止保留在.java文件中,会被被编译器忽略 RetentionPolicy.SOURCE
      • 标记的注解在编译时由编译器保留,即可以保留在.class文件中,由编译器处理,但会被Java虚拟机忽略 RetentionPolicy.CLASS
      • 标记的注解由JVM保留,因此运行环境可以使用它 RetentionPolicy.RUNTIME

      从存在的过程长短来看,SOURCE < CLASS < RUNTIME,也就是说CLASS包含SOURCE,RUNTIME包含CLASS和SOURCE。

    下面看一个例子
    //@Target(ElementType.TYPE) //只能在类上使用该注解
    @Target({ElementType.TYPE,ElementType.FIELD}) //允许在类、属性上使用该注解
    @Retention(RetentionPolicy.SOURCE) //注解仅保留到源码
    public @interface FirstAnnotation{
      
    }
    
    注解类型元素

    为自定义的注解包含注解类型元素声明,他们看起来很像方法,可以定义可选的默认值。

    @Target({ElementType.TYPE,ElementType.FIELD}) //允许在类、属性上使用该注解
    @Retention(RetentionPolicy.SOURCE) //注解仅保留到源码
    public @interface FirstAnnotation{
      String value(); //无默认值
      int age() default 1;// 有默认值
    }
    
    注解的应用场景

    根据元注解@Retention定义的注解存储方式,注解有三种使用场景。

    • SOURCE:作用与源码级别的注解,可提供IDE语法检查、APT等场景使用,注解在编译器编译后会被丢弃
    • CLASS: 会保留在class文件中,可以用于字节码操作,如AspectJ、热修复Roubust。
    • RUNTIME:注解保留至运行期,可结合反射技术使用。
    1、IDE语法检查

    可自定义一个注解来检查传入方法的参数的合法性,比如以下实例中,用注解@pet限制了方法的参数只能是DOG和CAT,如果传入其他的参数,会出现警告。在开发中可使用已经存在的注解,比如@DrawableRes 等。该方法与枚举相比,可节省不少内容空间,因为枚举的本质是对象,会比常量多5到10倍的内存占用。

    public class MyClass {
    
        public static void main(String[] args){
            MyClass myClass = new MyClass();
    
            myClass.havePet(DOG);
            //myClass.havePet(TIGER);//会出现参数非法警告
    
        }
    
        private static final int DOG = 0;
        private static final int CAT = 1;
        private static final int TIGER = 2;
    
        @IntDef({DOG,CAT}) //限定两个值
        @Target({ElementType.PARAMETER,ElementType.FIELD})
        @Retention(RetentionPolicy.SOURCE)
        @interface pet{
    
        }
    
        @pet
        private int myPet;
    
        /**
         * 用pet注解修饰后,该方法就只能接收由pet注解限定的两个参数值,
         * 如果传入其他的数值,会出现警告
         * @param pet
         */
        public void havePet(@pet int pet){
            myPet = pet;
        }
    
        //使用系统已经定义的注解
        /**
         * 只能传入drawable资源ID
         * @param id
         */
        public void haveDraw(@DrawableRes int id){
    
        }
    }
    
    2、APT注解处理器

    APT的全程是Annotation Processor Tools,翻译为注解处理器,注解处理器是javac自带的一个工具,用来在编译期间扫描注解信息,并将信息交由注解处理器处理。

    注解处理器是对注解应用最为广泛的场景,在Glide、EventBus3、Butterknifer、Tinker、Arouter等常用框架中都有注解处理器的身影。

    3、字节码操作(字节码插桩)

    定义为CLASS的注解,会被保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解),此时完全符合此种注解的使用场景为字节码操作,如Aspect、热修复Roubust中应用才场景。所谓字节码操作,是直接修改class文件已达到修改代码执行逻辑的目的。

    假设想在有一个模块,其中一部分方法需要登陆后才可以操作,其余的方法不需要登陆就可操作,如果挨个方法做判断,太繁琐,此时我们就可以借助AOP(面向切面编程)的思想将模块中的所有功能进行划分,需要登陆和不需要登陆两部分,即两个切面,定义注解来区分这两个切面。除此之后,还可以进行运行期权限判断等。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface Login{
    
    }
    
    @Login
    public void jumpA(){
        ///以下是登陆后才可调用的操作
    }
    
    public void jumpB(){
        //以下是不需要登陆就可以调用的操作
        
    }
    
    

    在以上代码中,编译生成的字节码中jumpA方法会被修改为如下内容:

    @Login
    public void jumpA(){
        if(this.isLogin){ ///如果已经登陆
            ///以下是登陆后才可调用的操作
        }else{
            ///如果未登录,跳转到登陆界面
        }
    }
    
    4、RUNTIME 注解保留到运行期,可以通过反射来获取和应用注解中的所有信息

    反射是Java被称为动态语言的关键,反射是运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法,对于任何一个对象,都能够调用他的任意属性和方法,并且能够改变他的属性。

    Java 的反射机制提供了以下功能

    • 在运行时构造任意一个类的对象
    • 在运行时获取或者修改任意一个类所具有的成员变量和方法
    • 在运行时调用任意一个对象的方法和属性

    Class

    反射始于Class,Class是一个了类,封装了当前对象所对应的类的信息,一个类中的属性、方法和构造器等,Class就是用来描述类的类。一个类可以有多个对象,但是一个类在JVM中只会有一个Class实例。

    获得Class对象的三种方式:

    • 通过类名获取 类名.class
    • 通过对象获取 对象名.getClass()
    • 通过全类名获取 Class.forName(全类名) 或者 ClassLoader.loadClass(全类名)

    判断是否为某个类的实例

    我们可以通过instanceof关键字来判断是否为某个类的实例,同时我们也可以借助反射中的Class对象的isInstance()方法来判断是否为某个类的实例,他是一个native方法。

    public boolean isAssignableFrom(Class<?> cls)
    

    通过反射创建对象的两种方式

    • 使用Class对象的newInstance()方法来创建Class对象对应类的实例

      Class<?> c = String.class;
      Object str = c.newInstance();
      
    • 先通过Class对象获取指定的构造器对象,再调用构造器对象的newInstance方法来创建实例。这种方法可以用指定的构造器来构造类的实例。

      //获取String所对应的Class对象
      Class<?> c = String.class;
      //获取String类带一个String参数的构造器
      Constructor constructor = c.getConstructor(String.class);
      //根据构造器创建实例
      Object obj = constructor.newInstance("test");
      

    反射获取构造器信息

    Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类) 
                                                                         Constructor[] getConstructors() -- 获得类的所有公共构造函数 
                                                                         Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有) 
                                                                         Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
    

    反射获取类的成员变量信息

    Field getField(String name) -- 获得命名的公共字段 
    Field[] getFields() -- 获得类的所有公共字段 
    Field getDeclaredField(String name) -- 获得类声明的命名的字段 
    Field[] getDeclaredFields() -- 获得类声明的所有字段
    

    反射获取类的方法信息

    Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法 
    Method[] getMethods() -- 获得类的所有公共方法 
    Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法 
    Method[] getDeclaredMethods() -- 获得类声明的所有方法
    

    获取类的方法后,可以使用invoke()来调用这个方法。

    利用反射创建数组

    数组在Java里比较特殊的一种类型,它可以赋值给一个Object Reference,其中的Array类为java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,他的原型是:

    public static Object newInstance(Class<?> componentType, int length);
    

    基于注解和反射完成View 的注入

    思路

    定义注解,标识需要借助反射完成初始化的View变量,所以注解的Target为Field,Retention为RUNTIME。在实现初始化的方法中,获取Activity的Class对象,反射获取所有的变量,然后遍历变量,并且筛选出被定义的注解修饰的变量,如果被匹配的注解标识,获取注解并得到View的ID,此后有两种方式完成初始化,一是直接调用传入的Activity的对象的findViewById方法完成View的初始化,二是反射获取findViewById方法,然后借助invoke调用完成初始化。以下是关键代码:

    public class InjectUtils {
        private static final String TAG = "InjectUtils";
        /**
         * 利用反射获取Activity的所有成员,筛选出被规定的注解修饰的变量,获取ID值,然后调用findViewById即可实例化View
         * @param activity
         */
        public static void injectView(Activity activity){
            Class<?> clazz = activity.getClass();
            //获取所有的Fields
            Field[] fields = clazz.getDeclaredFields();
            //遍历变量
            for (Field field : fields) {
                //判断变量是否被InjectView注解修饰
                if(field.isAnnotationPresent(InjectView.class)){
                    InjectView injectView = field.getAnnotation(InjectView.class);
                    int value = injectView.value();
                    Log.d(TAG,"value = "  + value);
                    try {
                        //方法1
    //                    Method findViewById = clazz.getMethod("findViewById",int.class);
    //                    findViewById.setAccessible(true);
    //                    Object view = findViewById.invoke(activity,value);
    //                    field.setAccessible(true);
    //                    field.set(activity,view);
                        //方法2
                        Object view = activity.findViewById(value);
                        field.setAccessible(true);
                        field.set(activity,view);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    定义的注解为:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InjectView {
        int value();
    }
    

    https://github.com/lxj-helloworld/annotation

    相关文章

      网友评论

          本文标题:注解及其三类应用场景

          本文链接:https://www.haomeiwen.com/subject/kxqscktx.html