注解
注解的作用和意义
注解本身没有任何意义,单独的注解就是一种注解,它需要结合其他如反射、插桩、架构设计、框架等才有意义;
Java 注解又称 Java 标注,是 JDK1.5 引入的一种注释机制,是元数据的一种形式,提供有关于程序但又不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
如何定义注解
@Target(ElementType.TYPE) // 声明注解作用域
@Retention(RetentionPolicy.CLASS) // 声明注解的生命周期 存在时间
public @interface BindPath {
String value() default ""; // 可以定义默认值
// String id();
}
@interface 关键字来声明一个注解;
使用 @BindPath 作用在类上面的时候,需要指定 value 的值, key 的值可以不用指定,因为有默认值;
@BindPath("")
public class MainActivity extends Activity {
}
// 当使用 value 关键字的时候,可以不用指定value作为 key 来赋值
// 如果不使用 value,例如使用 id 的时候,那么需要指定 id 作为key
// @BindPath(id = "")
// 多个元素的时候,需要都指定了
// @BindPath(value = "", id = "")
元注解
定义:注解上的注解;
@Target、@Retention 注解就是一个元注解,这个注解就是用来限定注解可以作用在哪些上面,例如: 类,属性,方法,方法参数等等;
当不指定 @Target 的时候,默认是可以作用在任务区域(类、方法、属性、方法参数)
ElementType.TYPE // 作用在类上面
ElementType.FIELD // 作用在属性上面
ElementType.METHOD // 作用在方法上面
ElementType.PARAMETER // 作用在参数上面
ElementType.CONSTRUCTOR // 作用在构造方法上面
ElementType.LOCAL_VARIABLE // 作用在局部变量上面
ElementType.ANNOTATION_TYPE
ElementType.PACKAGE
ElementType.TYPE_PARAMETER
ElementType.TYPE_USE
ElementType.MOUDLE
注解的分类(按照保留级别来分类 @Retention)
-
SOURCE 阶段 ;源码阶段,经过编译器 javac 变成 class 之后,就会被抹除掉;
-
CLASS 阶段; 字节码阶段,保留在 class 阶段,但是会在 JVM 加载这个 class 的时候 被忽略掉;
-
RUNTIME 阶段;运行阶段,保留在运行时阶段;
SOURCE < CLASS < RUNTIME,即:CLASS 包含了 SOURCE, RUNTIME 包含了 SOURCE 和 CLASS
注解的应用场景
- SOURCE阶段
- APT技术,在编译器能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类;
- IDE语法检查;
- CLASS阶段
- 字节码增强,在编译出 class 后,通过修改 class 数据以实现修改代码逻辑的目的。对于是否修改的区分或者修改为不同逻辑的判断可以使用注解;
- 运行时阶段
- 反射,在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定;
SOURCE阶段应用场景-APT技术
如何创建一个注解处理程序
-
Android工程下 创建一个 Java module
-
创建一个普通的 Java 类继承 AbstractProcessor,继承之后 这个类就是一个注解处理程序,Javac在编译的时候会对齐进行实例化,然后调用里面的process方法;
-
指定要处理的注解;
-
@SupportedAnnonationTypes("com.llc.example.BindPath")
- 将这个普通类进行注册,使其成为一个可用的注解处理器;
- 在 main 下面 创建 resources 文件路径,在 resources 下面 创建 META-INF 路径,在META-INF 路径下创建 services 路径;
- 在这个路径下创建 文件名为:javax.annotation.processing.Processer 的文件;
- 文件中写入注解处理程序的全类名;
- app 模块引入我们的注解处理程序
-
annotationProcessor project(':compiler')
在 Task : app:compileDebugJavaWithJavac 这个 Task 中,执行 process 方法的调用;
javac 会先调用注解处理程序,处理完注解之后,才会将 .java 处理成 .class
PS:也可以使用 autoservice,但是 autoservice 在 gradle5.0+ 的版本上 会有兼容性问题;
注解处理程序是怎么运行的?
注解处理程序运行在编译阶段(在编译的时候,javac 会帮我们实例化 LanceProcesser 这个类,并调用 process 方法)。
javac 在将 .java 编译成 .class 的时候,就会采集所有的注解信息,包装成一个 Element 节点,再由 javac 调起我们创建的注解处理程序。所以 注解处理程序不需要我们手动调用,它是由 javac 调用的;我们只需要给 javac 指明让它去执行注解处理程序。
注解处理程序有什么作用?
APT技术,在编译器能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类;
SOURCE阶段应用场景-IDE语法检查
IDE语法检查
IDE中声明的一些注解,用来做语法检查。当我们调用 setDrawable 方法的时候,会对传入的值进行校验;
使用自定义注解来进行语法检查
private static final int SUNDAY = 0;
private static final int MONDY = 1;
@IntDef({SUNDAY,MONDAY})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(SOURCE)
@interface WeekDay {}
使用时,IDE就会在编辑的时候 进行语法检查:
CLASS阶段应用场景-字节码增强
字节码增强
字节码中写代码,本质就是 .class -> IO -> byte[] 按照 .class 格式进行修改;
反射
概念
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
反射则是一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了,这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变她的属性。是 Java 被视为动态语言的关键。
获得 class 对象
-
通过类名获取 类名.class
-
通过对象获取 对象名.getClass()
-
通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)
判断是否为某个类的实例
一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法;
public native boolean isInstance(Object obj);
判断是否为某个类的类型
public boolean isAssignableFrom(Class<?> cls)
创建实例
通过反射来生成对象主要有两种方式
-
使用Class对象的newInstance()方法来创建Class对象对应类的实例。
-
Class<?> clazz = String.class; Object strObj = class.newInstance();
-
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
-
//获取String所对应的Class对象 Class<?> c = String.class;// 获取String类带一个String参数的构造器 Constructor constructor = c.getConstructor(String.class);//根据构造器创建实例 Object obj = constructor.newInstance("23333");System.out.println(obj);
获取构造器信息
得到构造器的方法
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:
public T newInstance(Object ... initargs)
获取类的成员变量(字段)信息
获得字段信息的方法
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() 方法来调用这个方法。invoke 方法的原型为:
public Object invoke(Object obj, Object... args)
Android中声明的 Parcelable 类型数组 如何反射获取?
Android 中 声明 Parcelable 类型数组,在反射的时候需要先获取field 中的 ComponentType,判断这个 Type 是不是 Parcelable 然后创建一个新的数组进行数据的复制之后,才能进行反射设值;
Class<?> componentType = field.getType().getComponentType();
if(field.getType.isArray() && Parcelable.class.isAssignableForm(componentType)) {
Objcet[] objs = (Object[]) obj;
Objcet[] objects = Arrays.copyOf(objs, objs.length,
(Class<? extends Object[]>)field.getType);
obj = objects;
}
field.setAccessible(true);
filed.set(activity, obj);
获取数组中的单个元素类型:
Class<?> componentType = field.getType().getComponentType();
注解 + 反射 实战(自动 findViewById)
声明需要被处理的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView {
@IdRes int value();
}
使用这个注解
public class MyActivity extends Activity {
@InjectView(R.id.tv)
TextView txtView
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);
setContentView(R.layout.activity_main);
InjectUtils.inectView(this)
}
}
写一个 Utils 来处理这个注解,并调用 findViewById 方法
public class InjectUtils {
public static void injectView(Activity activity) {
Class<? extends Activity> cls = activity.getClass();
// 获得此类所有的成员
Field[] declaredFields = cls.getDeclaredFields();
for (Field filed : declaredFields) {
// 判断属性是否被InjectView注解声明
if (filed.isAnnotationPresent(InjectView.class)) {
InjectView injectView = filed.getAnnotation(InjectView.class);
// 获得了注解中设置的id
int id = injectView.value();
View view = activity.findViewById(id);
// 反射设置 属性的值
filed.setAccessible(true); //设置访问权限,允许操作private的属性
try {
// 反射赋值
filed.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
欢迎三连
来都来了,点个关注、点个赞吧~~