反射
前言
学习Java的反射机制是为了理解Apache Commons Collections中的反序列化漏洞做准备的。
反射概念
反射是一种间接操作目标对象的机制,允许程序在运行时获取类的信息,并且在运行时动态地创建对象、调用方法、访问字段等等;对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的方法/访问属性。
实质就是得到一个Class对象后,反向获取Class对象的对象。
Class类、Class对象与class关键字
Class类
是java中的一个类,位于java.lang包中。它提供了用于获取类信息和操作类或对象的属性和方法的方法。
Class对象
是JVM在运行时保留的每个类的描述信息。Class对象包含了该类的所有信息,包括类的名称、属性、方法、构造函数等。Class对象可以通过Class类的各种方法获取。
class
是java的关键字,用于声明类。
对Class类解读:
我们通常认为类是对象的抽象和集合,Class就相当于是对类的抽象和集合。
也可以认为对象是类的实例,类是Class的实例。
获取Class对象
通常有以下几种方法获取一个类的Class对象
Class.forName()
1 |
|
Test.class
1 |
|
obj.getClass()
1 |
|
但在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。
实例化对象
获取到Class之后,实例化对象,newInstance()
方法调用无参的构造器创建对象。
(Class类中的newInstance()
方法)
java.lang.relect.Constructor
类里也有一个newInstance()
方法可以创建对象,该方法和Class类中的newInstance()
方法很像,但是相比之下,Constructor类的newInstance()
方法更加强大,我们可以通过这个newInstance()
方法调用有参数的和私有的构造方法。
这时有个问题,比如一个类有很多的构造方法,我们怎么能找到我们想要的构造方法呢?我们可以使用getConstructor()
,它根据提供的参数类型来定位特定的构造函数。可以从getConstructor()
的函数声明中看到,参数类型是Class可变长参数。
因为Test类中有两个属性,Sting name和int age,getConstructor()
填入String.class和int.class,发现报错了,报错信息提示没有这个方法
可以看到Test类中age属性是私有的,这时使用getDeclaredConstructor()
方法。
设置访问权限为true。就可以通过有参构造函数实例化对象。
获取类的属性
通过getFields()
可以获取属性,但是发现这里只获取了name,age并没有被获取,原因其实和上面类似,必须用getDeclaredFields()
并设置访问权限。
set()
方法通常和getField()
搭配使用,set()
方法是Field
类的一部分,用于通过反射机制设置对象的字段值,具体一点就是Field
对象表示类中的一个成员变量,set()
方法允许修改这个字段的值,即使该字段是私有的。在使用之前,通常需要调用setAccessible(true)
来绕过Java的访问控制检查。
age属性是私有的
(通过getDeclaredField()
设置访问权限)
调用类的方法
Test类有public Hello方法和Private prHello方法。
通过getMethods()
可以获取公共方法。
通过getDeclaredMethods()
可以获取公共方法和私有方法
getMethod()
通常和invoke()
搭配,invoke()
可以动态地在运行时调用对象的方法。其实也不难理解,我们通过getMethod()
反射获得一个方法后,肯定需要指定是哪个类,并且指明执行的参数。
用getMethod()
调用一下Test类中的Hello()
方法,可以发现它调用的是无参的方法,和实例化对象的情况类似,报错了,需要指明它的类型,否则调用无参的方法。
(Class类中的getMethod()
方法)
小练习,反射弹计算器
正常情况下咱们弹计算器
反射写法
因为 getRuntime()
是静态方法,属于类,所以getRuntime.invoke()
;不需要指定对象。
1 |
|
完整代码
1 |
|