注解与反射

注解

声明一个注解类型
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();
                   }
               }
           }
       }
   } 
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容