注解(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();
}
网友评论