在看这篇文章之前对于不知道什么是注解的建议先看上一篇《JAVA注解》 穿越门 ,如果知道的话就可以跳过了。

一. 概述

首先在讲运行时注解之前,有必要先说一下注解其存在周期。对于JAVA自定义注解其存在的周期主要和其元注解

@Retention
复制代码

的赋值有关。

元注解的赋值一共有如下三种:

  • RetentionPolicy.SOURCE( 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。)
  • RetentionPolicy.CLASS (注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 )
  • RetentionPolicy.RUNTIME (注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。)

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码

我们的运行时注解对应标注为

@RetentionPolicy.RUNTIME
复制代码

的注解,即代码在内存中运行时可获取处理的注解。

二.定义添加注解

2.1 自定义注解

本次自定义注解目标为实现一个汽车类信息注解(CarInfo),该注解作用于Car类的值上。可实现不使用set方法,通过注解给该值添加对象并完成初始化的功能。

实现该注解第一步需在annotation包下自定义一个***CarInfo***的注解,代码如下

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CarInfo {
    String name () default "" ;
    int size () default 0 ;
}
复制代码

从元注解的信息可知,该注解可作用于值,生命周期到运行时一直存在,该注解主要包括车名和车数量两个内容。

2.2 添加注解

在MainActivity类中定义一个Car类的值,然后在他上面添加CarInfo注解,并添加name和size相关属性。添加完该注解以后,我们在代码运行时就能获取car值上的注解内容了。

@CarInfo(name = "BMW",size = 100)
    Car car;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        //这里我们要首先注册一下这个类
        AnnotationCar.instance().inject(this);
        //当程序运行的时候这里将会输出该类Car的属性值。
        Log.e("WANG","Car is "+car.toString());
    }

复制代码

三.处理注解

运行时注解核心的内容就是注解处理。 注解处理有两个核心问题:

  1. 如何通过代码去获取注解?
  2. 什么时候去获取注解?

对于问题一,答案就是通过反射去获取注解。 思路大概如下:

  1. 根据传入的对象获取到该对象对应的类。
  2. 通过该类获取到类中的所有值
  3. 从这些值中筛选添加了CarInfo注解的值
  4. 获取到这些值上的注解,根据注解内容,给该对象初始化赋值。

对于问题二,具体问题需要具体分析,不过一般可把时机放早点。我放在onCreate方法内。

no code no truth

注解处理相关代码

public class AnnotationCar {
    private static AnnotationCar annotationCar;
    public static AnnotationCar instance(){
        synchronized (AnnotationCar.class){
            if(annotationCar == null){
                annotationCar = new AnnotationCar();
            }
            return annotationCar;
        }
    }

    public void inject(Object o){
        Class<?> aClass = o.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field:declaredFields) {
            if(field.getName().equals("car") && field.isAnnotationPresent(CarInfo.class)) {
                CarInfo annotation = field.getAnnotation(CarInfo.class);
                Class<?> type = field.getType();
                if(MainActivity.Car.class.equals(type)) {
                    try {
                        field.setAccessible(true);
                        field.set(o, new MainActivity.Car(annotation.name(), annotation.size()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

复制代码

注解注入过程

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        //这里我们要首先注册一下这个类
        AnnotationCar.instance().inject(this);
        //当程序运行的时候这里将会输出该类Car的属性值。
        Log.e("WANG","Car is "+car.toString());
    }

复制代码

运行结果

2018-12-25 17:07:09.935 12026-12026/android.weifeng.com.annotationtest E/WANG: Car is Car{name='BMW', size=100}
复制代码

获取到了注解中的内容,注解成功。

四.思考

  1. 运行时处理注解内容过程需要大量用到Java反射,性能必然受影响。
  2. 运行时注解注入放到onCreate中,会影响该Activity的启动时间,但是许多情况需要又不得不放在onCreate中处理。
  3. 针对上面的问题,可以使用编译时注解来代替运行时注解。编译时注解是在代码编译阶段就生成了相应的代码,不存在大量反射的问题,同时在onCreate中代码运行时间也可以缩短。