美文网首页
注解与反射

注解与反射

作者: 沪漂意哥哥 | 来源:发表于2022-06-16 22:51 被阅读0次

    注解

    声明一个注解类型
    public @interface Lance{
    }
    
    元注解

    在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta�annotation(元注解)。

    • @Target
      注解标记另一个注解,以限制可以应用注解的 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 虚拟机(JVM)会忽略。
      RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。

    @Retention 三个值中 SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、
    CLASS。下文会介绍他们不同的应用场景。

    注解应用场景

    SOURCE

    RetentionPolicy.SOURCE ,作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
    在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。

    • IDE语法检查
      在Android开发中, support-annotations 与 androidx.annotation) 中均有提供 @IntDef 注解,此注解的定义如下:
    @Retention(SOURCE) //源码级别注解 
    @Target({ANNOTATION_TYPE}) 
    public @interface IntDef { 
    int[] value() default {}; 
    boolean flag() default false; 
    boolean open() default false;
    }
    

    Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。
    比常量多5到10倍的内存占用。此注解的意义在于能够取代枚举,实现如方法入参限制。

    如:我们定义方法 test ,此方法接收参数 teacher 需要在:Lance、Alvin中选择一个。如果使用枚举能够实现为:

    public enum Teacher{
    LANCE,ALVIN
    }
    public void test(Teacher teacher) {
    }
    

    而现在为了进行内存优化,我们现在不再使用枚举,则方法定义为:

    public static final int LANCE = 1; 
    public static final int ALVIN = 2; 
    public void test(int teacher) {
    }
    

    然而此时,调用 test 方法由于采用基本数据类型int,将无法进行类型限定。此时使用@IntDef增加自定义注解:

    public static final int LANCE = 1; 
    public static final int ALVIN = 2; 
    @IntDef(value = {MAN, WOMEN}) //限定为LANCE,ALVIN @Target(ElementType.PARAMETER) //作用于参数的注解 @Retention(RetentionPolicy.SOURCE) //源码级别注解 
    public @interface Teacher { 
    }
    public void test(@Teacher int teacher) { 
    }
    

    此时,我们再去调用 test 方法,如果传递的参数不是 LANCE 或者 ALVIN 则会显示 Inspection 警告(编译不会报错)。

    以上注解均为 SOURCE 级别,本身IDEA/AS 就是由Java开发的,工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。

    • APT注解处理器

    APT全称为:"Anotation Processor Tools",意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
    注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
    关于注解处理器的实现,在后续课程中会有相当多的介绍。此处先不进行详细介绍。

    CLASS

    定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
    所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。


    image.png

    如果我们使用普通的编程方式,需要在代码中进行 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)); 
    }
    

    在上诉代码中, jumpA 方法需要具备登录身份。而 Login 注解的定义被设置为 CLASS 。因此我们能够在该类所编译的字节码中获得到方法注解 Login 。在操作字节码时,就能够根据方法是否具备该注解来修改class中该方法的内容加入 if-else 的代码段:

    //Class字节码 
    @Login 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

    注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

    反射

    一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

    反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。

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

    Java反射机制主要提供了以下功能:

    • 在运行时构造任意一个类的对象
    • 在运行时获取或者修改任意一个类所具有的成员变量和方法
    • 在运行时调用任意一个对象的方法(属性)
    Gson反序列化
    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; 
     }
    
     @Override 
     public String toString() { 
       return "Data{" + "result=" + result + '}'; 
     } 
    }
     public static void main(String[] args) { 
       Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");  
       Gson gson = new Gson(); String json = gson.toJson(dataResponse);   
       System.out.println(json);
       //为什么TypeToken要定义为抽象类?
       Response<Data> resp = gson.fromJson(json,   new TypeToken<Response<Data>>() { }.getType());   
       System.out.println(resp.data.result); 
    }
    

    在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么TypeToken 要被定义为抽象类呢?
    因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型signature信息记录到Class元数据中。

    控件绑定

    /**
    *
    */
    public class InjectViewUtils {
    
       public static void injectView(Activity activity) {
           if (null == activity) return;
    
           Class<? extends Activity> activityClass = activity.getClass();
           Field[] declaredFields = activityClass.getDeclaredFields();
           for (Field field : declaredFields) {//获取Activity类里面声明的所有成员变量
               if (field.isAnnotationPresent(InjectView.class)) {//找出标注了@InjectView的成员变量
                   //解析InjectView 获取button id
                   InjectView injectView = field.getAnnotation(InjectView.class);
                   int value = injectView.value();//获取变量的值,也就是控件的id
    
                   try {
                       //找到findViewById方法
                       Method findViewById = activityClass.getMethod("findViewById", int.class);
                       findViewById.setAccessible(true);
                       Object view = findViewById.invoke(activity, value);
                       field.set(activity, view);
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
               }
           } 
       }
    
       public static void injectAutowired(Activity activity) {
           Class<? extends Activity> cls = activity.getClass();
           //获得数据
           Intent intent = activity.getIntent();
           Bundle extras = intent.getExtras();
           if (extras == null) {
               return;
           }
    
           //获得此类所有的成员
           Field[] declaredFields = cls.getDeclaredFields();
           for (Field field : declaredFields) {
               if (field.isAnnotationPresent(Autowired.class)) {
                   Autowired autowired = field.getAnnotation(Autowired.class);
                   //获得key
                   String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();
    
                   if (extras.containsKey(key)) {
                       Object obj = extras.get(key);
                       // todo Parcelable数组类型不能直接设置,其他的都可以.
                       //获得数组单个元素类型
                       Class<?> componentType = field.getType().getComponentType();
                       //当前属性是数组并且是 Parcelable(子类)数组
                       if (field.getType().isArray() &&
                               Parcelable.class.isAssignableFrom(componentType)) {
                           Object[] objs = (Object[]) obj;
                           //创建对应类型的数组并由objs拷贝
    
                           Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
                           obj = objects;
                       }
    
                       field.setAccessible(true);
                       try {
                           field.set(activity, obj);
                       } catch (IllegalAccessException e) {
                           e.printStackTrace();
                       }
                   }
               }
           }
       } 
    }
    
    

    相关文章

      网友评论

          本文标题:注解与反射

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