注解
1. 什么是注解:
- JAVA注解 Annotation 又称为JAVA标注(标签),是JDK5引入的一种注释机制。是元数据的一种形式,用来提供有关于程序但不属于程序本身的数据;注解对于他们注解的代码的操作没有直接的影响。
- 元数据:元数据即描述数据的数据,一般是结构化的数据(如存储在数据库里的数据,规定了字段的长度,类型等);元数据一般由两部分组成,分别是:元数据项目和元数据内容;元数据项目是从信息资源中抽取出来的用于说明其特性、内容的结构化的数据(很常见如一本书,有书名,版本,出版数据等,这些就是元数据项目),是用来组织、描述、检索、保存、管理信息和知识资源的;而元数据内容则是表明项目内容的。
2. 注解的意义:
- 单独的注解就等同于注释,没有任何意义;只有在注解结合其他的技术时(如反射、插桩等)才会存在意义
3. 注解的声明:
-
public @interface XXX { }
public @interface test { String value(); //无默认值 int age() default 1; //有默认值 }
注意:在使用注解时,如果定义的注解中的元素类型无默认值,则在使用注解时,必须进行传值
@test("注解示例") //当其中需要传值的元素为value时,可以省略 元素名 = @test(value = "注解示例", age = 2) int i;
4. 元注解:
-
所谓的元注解其实就是用来注解其他注解的注解,这类对注解类型进行注解的注解类,我们就称之为metaannotation(元注解)。
-
元注解的类别:
-
@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 :在任何声明类型的地方使用
-
-
-
@Retention
-
用于指定注解的保留时长
- RetentionPolicy.SOURCE :标记的注解仅保留在源码级别中,会被编译器忽略,即编译后就不存在
- RetentionPolicy.CLASS :标记的注解在编译时由编译器保留,但JAVA虚拟机(JVM)会忽略,不载入虚拟机,即class文件中注解仍存在,但实际执行时不再存在
- RetentionPolicy.RUNTIME :标记的注解由JVM保留,在运行时仍旧可以使用反射机制进行获取
其中,SOURCE < CLASS < RUNTIME,即CLASS包含SOURCE,而RUNTIME包含SOURCE和CLASS
-
这三种注解保留级别,对应的应用场景是什么呢?
级别 应用技术 说明 源码(SOURCE) APT、IDE语法检测 在编译期能够获取注解和注解声明的类,包含类中所有的成员信息,一般用来生成额外的辅助类 字节码(CLASS) 字节码增强 在编译出Class后,通过修改Class数据以实现修改代码逻辑的目的,对于是否需要修改或者修改为不同逻辑的判断可以使用注解完成 运行时(RUNTIME) 反射 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判断 -
应用举例:
-
SOURCE
- IDE 语法检查
/** * 应用于IDE参数类型检查 **/ @Target({ElementType.FIELD,ElementType.PARAMETER}) @Retention(RetentionPolicy.SOURCE) @IntDef(value = {demo_01.TV, demo_01.phone}) //这里限制了参数类型 @interface IDEcheck { } public class demo_01 { public static final int TV = 1; public static final int phone = 2; public static void chooseProduct(@IDEcheck int i) { } public static void main(String[] args) { chooseProduct(TV); } }
-
APT:Annotation program tools,即注解处理工具,我们通过使用对应注解从而在注解处理工具中进行一些辅助操作,而因为这个注解处理程序是在javac中进行处理的,故相对应的注解则只需要在源码中进行保存,只需要使用SOURCE级别即可。
-
注解处理器运行在编译阶段;我们的编译流程:.java文件 —> (通过javac编译) —> .class文件;而在javac对*.java*文件进行编译时,就会先采集所有的注解信息,打包成一个节点,进而去解析注解处理程序;所以,注解处理程序都是由JAVAC直接调用处理的!
-
注解处理程序的实现步骤
-
在对应工程下创建JAVA模块
- 之所以是JAVA模块是因为,注解处理程序是由JAVAC进行调用解析的,而JAVAC是用来编译java文件的,所以需要创建为JAVA模块
-
继承 AbstractProcessor
- JDK提供的,代表该类为注解处理程序,JAVAC则能够在编译时,调用其中的process方法;类比于MainActivity,之所以是Activity,是因为MainActivity继承了Activity类
-
增加配置文件,在对应java模块下的,main文件下创建以下对应名称和目录关系的文件夹:
再在services文件夹下创建文件,文件名为:javax.annotation.processing.Processor;并在此文件中增加对应注解程序的全类名
- 第二步继承了AbstractProcessor之后,JAVAC还是不会去调用,就类似于MainActivity继承了Activity后,如果不在AndroidManifest.xml里面去注册,仍旧无法被调用一样;
-
实现注解处理程序中的process
- 此处即可按照自己的需求进行注解处理程序的编写
-
标记注解处理程序,使其只在对应注解调用的时候去执行
@SupportedAnnotationTypes("全类名")
在注解处理程序process上增加该元注解,标记只在使用到该特定注解时会调用对应的注解处理程序
-
在app模块中,build.gradle文件中导入该JAVA模块
annotationProcessor project(':XXXX')
和导入其他三方库的方式是一致的
-
-
-
CLASS
- 字节码增强:所谓的字节码增强本质上实际上就是在字节码中写代码,如字节码插装技术,很多时候就是通过注解来进行区分是否需要对相应函数进行代码修改,而因为是在class文件中进行操作,所以注解需要至少保留到class文件中
-
RUNTIME
-
在反射中可以结合注解进行参数的赋值等操作,此时的注解相当于一个标签,通过这个标签我们可以很容易的判别究竟哪个成员是我们需要操作的。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface source_annotation { @Idres int value(); } public class Inteject { public static void inject(Activity activity) { Class<? extends Activity> cls = activity.getClass(); Field[] fields = cls.getFields(); for (Field field : fields) { //判断成员变量上是否被注解标识 if (field.isAnnotationPresent(source_annotation.class)) { int id = field.getAnnotation(source_annotation.class).value(); TextView view = activity.findViewById(id); field.setAccessible(true);//因为不清楚是否是私有的变量,所以设置权限,可被修改 try { //反射赋值 /* * 反射的时候,第一个参数:在哪个对象上去设置属性值或者调用方法 * 如果当前的属性是非static的,那么就需要传入实例对象; * 而如果是static的,那么就可以传入null,因为直接通过类就可以拿到属性 */ field.set(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } // 完成通过反射自动注入view参数 public class MainActivity extends AppCompatActivity { @source_annotation(R.id.tv) TextView view; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //反射进行赋值 Inteject.inject(this); view.setText("11111111"); } }
-
-
-
-
-
反射
1. 什么是反射:
-
反射的定义:
- 反射即在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意方法和属性,并且可以改变它的属性
-
反射的使用场景:
- 一般情况下,我们使用某个类的时候必定是知道它是个什么类,是用来做什么的,并且能够获得此类的引用,这样我们就可以直接对这个类进行实例化,进而可以使用类对象进行操作;而反射则是用于在不知道将要初始化的类对象是什么,无法使用new关键字进行对象创建时,这时候我们就可以使用JDK提供的反射API进行反射调用;
2. 反射实现原理:CLASS类
- 反射始于Class,Class是一个类,封装了当前对象所有对应的类的信息。一个类中存在属性,方法,构造器等,而不同的类拥有不用的成员,那么如果当前需要一个类,用来描述这所有的不同的类,这就是Class类,Class是用来描述类的类!
- Class类是一个对象照镜子的结果,对象可以看到自己拥有哪些属性、方法、构造器、实现了哪些接口等等。而对于每一个类来说,JRE都为其保留了一个不变的Class类型对象。一个Class对象包含了特定某个类的有关信息。一个类(而非一个对象)在JVM中只会存在一个Class实例!
- 即,当我们定义了一个类的时候,JVM就为这个类创建了唯一的一个Class实例,我们可以通过获取对应类的Class实例,从而访问到对应类中的所有成员!
3. 获取Class对象的三种方式
-
通过类名获取:
类名.class
public class baseClass {} public void main() { Class cls = baseClass.class; }
-
通过对象获取:
对象名.getClass()
public class baseClass {} public void main() { baseClass base = new baseClass(); Class cls = base.getClass(); }
-
通过全类名获取:
Class.forName(全类名)
或者classLoader.loadClass(全类名)
package com.example.reflect_demo; public class baseClass {} public void main() { Class cls1 = Class.forname("com.example.reflect_demo.baseClass"); Class cls2 = ClassLoader.loadClass("com.example.reflect_demo.baseClass"); }
4. instanceof / isInstance
-
一般的,我们可以使用
instanceof
关键字来判断相应对象是否为某个类的实例。同时,我们也可以借助反射中Class对象的isInstance(Object obj)
方法来判断是否为某个类的实例,通过isAssignableFrom(Class<?> cls)
来判断该class是否为某个类的类型-
public native boolean inInstance(Object obj) public boolean isAssignableFrom(Class<?> cls)
-
这两者的区别在于,一个判断的参数是类,一个是对象;效果上类似
-
5. 通过反射创建实例的两种方式
-
使用Class对象的
newInstance()
方法来创建Class对象对应类的实例Class<?> c = String.class; Object str = c.newInstance();
- 注意:
newInstance()
方法调用的是无参构造函数,如果当前类定义了有参构造,且没有再声明无参构造,即当前类没有无参构造函数,那么调用newInstance()
去反射生成类就会报错!
- 注意:
-
先通过Class对象获取指定的
Constructor
对象,再调用Constructor
对象的newInstance()
方法来创建实例。这种方法好处在于:可以使用指定的构造器构造类的实例//获取String所对应的Class对象 Class<?> cls = String.class; //通过Class对象获取String类中带一个String参数的构造器 Constructor constructor = cls.getConstructor(String.class); //根据构造器创建实例 Object obj = constructor.newInstance("111"); System.out.println(obj);
6. 反射获取构造器
- Class类中的获取构造器的方法(以下所有的方法都是只能够拿到当前类的,无法获取父类的构造器):
Constructor getConstructor(Class[] params)
- 获得使用特殊的参数类型的public构造函数
Constructor[] getConstructors()
- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params)
- 获得使用特定参数类型的构造函数(包括私有的)
Constructor[] getDeclaredConstructors()
- 获得类的所有构造函数
7. 反射获取类的成员变量
-
Class类中获取成员变量的方法(需要注意:当这个成员变量为private时,get/set都需要先将其setAccessible(true))
Field getField(String name)
- 获取特定的公共成员变量
Field[] getFields()
- 获取类中的所有公共的成员变量
Field getDeclaredField(String name)
- 获得使用特定的成员变量(包括私有的)
field[] getDeclaredFields()
- 获得所有的成员变量(包括私有的)
-
注意:当我们获取到成员变量,想要获取值或者反射设置值时,调用的方法是:
/* 获取当前参数的值 */ public native Object get(Object obj); /* 设置当前参数的值 */ public native void set(Object obj, Object value); /*这两个方法中的 Object 参数,代表的是对应对象,即要获取哪个对象中的对应变量的值,以及要将对应数值设置到哪个对象的对应变量中;那么如果: 1、当前的属性是非static的,那么就需要传入实例对象; 2、当前的属性是static的,那么就可以传入null,因为直接通过类就可以拿到属性*/ Class<?> Cls = this.getClass(); Field[] fields = Cls.getDeclaredFields(); for (Field field : fields) { String value = field.get(this); field.set(this, "XXX"); }
8. 反射获取类中的成员方法
-
Class类中获取成员方法的函数
Method getMethod(String name, Class[] params)
- 获取特定的参数类型的公共成员方法
Method[] getMethods()
- 获取所有的公共成员方法
-
Method getDeclaredMethod(String name, Class[] params)
- 获取特定的参数类型的成员方法(包括私有的)
-
Method[] getDeclaredMethods()
- 获取所有的成员方法
-
当我们从类中获取了一个方法后,我们就可以使用
invoke()
方法去调用这个方法public Object invoke(Object obj, Object... args)
9. 反射机制的主要功能:
- 在运行时构造任意一个类的对象
- 在运行时获取或者修改任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法或属性