android study 5-java
java反射机制
1.什么是java反射
java反射是指在运行状态中,对于任意类,都可以得到这个类的属性,方法信息,以及得到这个类的对象。对于任意对象,都可以调用它的方法,以及访问它的属性。这种动态获取信息,动态调用对象方法的功能叫做java的反射机制。
1.1 java反射使用场景
比如不知道类或者对象的具体信息,比如类的名称放在某个文件中,需要运行时读取文件以动态获取类的信息。或者注入后想要覆写函数地址完成hook,也可以通过java反射来完成。
1.2 基础知识
在JVM中,会为每个加载的类维护一个类对象,然后根据类对象来创造实例对象或者提供静态变量。
class对象的获取:
1. 通过对象.getClass()
2. 通过类.class字面量
3. 通过class类的forName方法
4. 通过类加载器的loadClass
那么如何对class对象进行操作呢?先获取到类型的Class对象,通过Class对象的newInstance方法可以得到类的实例。通过Class对象可以获取到Constructor对象,进一步可以使用Constructor对象来得到类的实例。通过Class对象可以获取到Method对象,通过Method的invoke方法可以调用一些方法。通过Class对象可以获取到Field对象,我们可以对这个实例的一些字段进行赋值取值操作。流程如下
1 | actionClass = Class.forName(“ MyClass”); |
2.动态代理与静态代理
代理本身不实现具体服务,只是利用委托类完成服务,在此过程中对输入输出做一些处理。
2.1 静态代理
静态代理中,代理类和被代理类会实现同样的接口。代理类同时会持有有被代理类的对象。可以通过调用代理类的方法以调用被代理类的对应方法,相当于可以增加一些前置准备工作。比如给该方法添加一个计时的操作。
2.2 动态代理
动态代理类需要实现InvocationHandler接口以执行动态代理,需要重写invoke函数以实现动态代理。里面是具体的代理服务。动态代理具体步骤如下:
1.通过实现InvocationHandler接口创建自己的调用处理器
2. 通过为Proxy指定ClassLoader对象和一组interfaces来创建动态代理类
3. 通过反射机制获得动态代理类的构造函数,唯一参数是调用处理器的接口类型
4. 通过构造函数创建动态代理类型实例,构造时调用器类型作为参数传入(具体来说,是jdk根据传入的参数信息,在内存中动态创建.class文件,然后通过newInstance创建动态代理类的实例)
Java动态代理实际上通过反射技术,把代理对象和被代理对象(真实对象)的代理关系建立延迟到程序运行之后,动态创建新的代理类去完成对真实对象的代理操作(可以改变原来真实对象的方法行为),这一点成为了当前主流的AOP框架和延迟加载功能的基础。
3.双亲委派
3.1 类的初始化
大多数情况下,类加载后直接就初始化了。以下三种情况是,类加载后,会延迟初始化。
1.通过子类调用父类的静态成员时,只需要完成父类的初始化,子类初始化被延迟了。
2.使用某个类的静态常量时,可以延迟类的初始化。
3.使用某个类声明并创建数组时,如果还未真正创建该类的对象时,可以延迟类的初始化
以上三种以外的,都会直接对类进行初始化:
1.new对象
2.调用某个类的静态变量
3.通过反射的API调用该类成员等
4.main方法所在的类,一定先初始化完成,再执行main方法的。
5.当子类初始化时,如果父类还未初始化,会先初始化父类。
3.2 类加载器
类加载器的类型是java.lang.classloder,是负责类的初始化工作。
1.引导类加载器(Bootstrap ClassLoader),由C语言编写,负责加载最核心的类库。
2. 扩展类加载器(Extension ClassLoader),负责加载扩展库。JRE的lib中的ext目录下的扩展库
3. 应用程序类加载器(Application ClassLoader),负责加载用户自定义的类型,在classpath下的类
4. 自定义类加载器。加载classpath以外的类,或者需要在加载时做一些操作如解密,这时候需要使用自定义类加载器。
3.3 双亲委托模式
应用程序类加载器把扩展类加载器视为双亲,扩展类加载器把引导类加载器 视为双亲。其原理是当JVM接到一个”类的加载任务时“,AppClassLoader先相应,它会在当前方法区找找看,如果加载过了,就不会重复加载。
如果发现这个类没有加载过,它会将加载类的任务再次提交给”parent“,此时就是引导类加载器,他也会先找一下是不是被加载过了。加载过了就不再重复加载了。
如果引导类加载器发现这个类没有加载过,它就把加载类的任务提交给自己的”parent“,此时就是JRE核心类库的rt.jar,如果自己负责的目录下有,就正常加载,返回Class对象即可。
如果引导类加载器在自己负责的目录下找不到这个类,就会把任务传给ExtClassLoader,ExtClassLoader接下来也在自己负责的目录下尝试加载,此处时JRE的lib中的ext目录下的扩展库,如果自己负责的目录下有,就正常加载,返回Class对象即可。
如果AppClassLoader在自己负责的目录下还找不到这个类,就报异常ClassNotFoundException异常。
如果在前面的加载过程中,加载失败会报NoClassDefFoundError,ClassFormatError等。
这样做的好处是安全,用户定义的类就不会覆盖到核心类库中的类。