使用场景
反序列化通常就是通过一个类的函数来调用另一个类的函数,从而构成链子
像一般调用函数,最基本的肯定是
|
|
但是如果类的构造函数是private修饰
那么
person person = new person();实例化会报错
报错:java: person(java.lang.String,int) 在 person 中是 private 访问控制
再或者person类的toString函数是private或者protected修饰
person.toString(); 也会报错
所以需要通过其他方式来实现,这也就是反射做的事
construct实例化对象
- Constructor类型存储getConstructor获取的有参或无参构造函数。其中getConstructor参数为需要获取的构造函数形参类型,如String.class,Class[].class
- newInstance实例化对象,可从原型实例化对象,如person.getClass().newInstance()
当然这种实例化只能调用无参构造函数实例化。可以先获取构造器,再调用有参构造函数实例化,如:
|
|
|
|
- **getDeclaredConstructor(String.class,int.class)**是获取私有构造函数,参数要对应着构造函数的参数
- **personconstructor.setAccessible(true);**构造函数是私有的一定要加上
- 如果
toString
函数是私有的,**p.toString()**还是会报错
而要能调用私有函数就需要Field和Method
Field设置属性值
|
|
如果属性是私有的就需要以上方法加declared获取私有内容,如getDecalredFields()获取私有属性
使用setAccessible(true)允许修改私有变量,私有方法等private内容
|
|
Method调用函数
假如这里修改一下person类的toString函数
|
|
变私有了,而且增加了参数
|
|
反射利用
反序列化需要从能invoke执行代码的部分走到readObject,序列化时导致任意代码执行。
比如Runtime类下的exec方法就能执行系统命令,我们通常使用单形参的这个exec:
java以是否有static关键字区分了实例方法和静态方法。
- 实例方法:必须通过已经实例化的对象来调用。例如,如果你有一个名为MyClass的类,并且它有一个实例方法doSomething(),你需要先创建MyClass的一个实例,然后通过这个实例来调用方法,如
MyClass instance = new MyClass(); instance.doSomething();
。 - 静态方法(可以直接调用的方法):可以通过类名直接调用,无需创建类的实例。例如,如果MyClass有一个静态方法staticMethod(),你可以直接通过类名调用它,如MyClass.staticMethod();
Runtime.exec()就是实例方法,需要在实例化对象上进行调用。Runtime.getRuntime()返回一个单例实例。
因为Runtime类没有公开的构造方法,getRuntime()方法实际上是返回了该类的一个单例实例。这样做可以确保整个应用程序中只有一个Runtime实例存在,这样可以更好地管理资源和环境交互
所以使用Runtime.getRuntime().exec("calc");
就能调用系统计算器。
但是Runtime类没有继承Serializable,所以不能序列化。InvokerTransformer继承了Serializable,且其transformer调用了invoke,有invoke当然就能用invoke反射调用到Runtime中的exec。
反射知识博客
先把Runtime.getRuntime.exec("calc");
改为反射调用:
其中invoke调用格式为:方法.invoke(对象,参数)
getMethod调用格式为:对象.getMethod(方法名,该方法参数类型)
|
|
getRuntime()是不是也能顺便改成反射了呢?
反射操作
|
|
正向操作
|
|
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。
这时候,我们使用 JDK 提供的反射 API 进行反射调用
|
|
- 这行代码通过
Class.forName()
方法动态加载名为com.chenshuyi.reflect.Apple
的类。注意,类的全限定名(包括包名)必须正确,才能成功加载。返回值clz
是Apple
类的Class
对象,它包含了关于这个类的所有元信息。
|
|
- 通过
clz.getMethod()
方法,获取Apple
类中的setPrice
方法。getMethod()
需要传递方法名称以及该方法的参数类型,在这里,setPrice
方法接受一个int
类型的参数。因此,int.class
表示参数的类型。
|
|
- 获取
Apple
类的无参构造函数(默认构造函数)。getConstructor()
方法用于获取类的构造方法,若不传参数,则获取无参构造器。
|
|
- 使用上面获取的无参构造函数,创建
Apple
类的实例对象。newInstance()
方法用于调用构造器并创建实例,返回值为Object
类型。
|
|
- 通过
Method
对象method
调用反射的setPrice
方法,并传入参数4
。invoke()
方法的第一个参数是目标对象(即Apple
类的实例object
),后面是要传递给方法的参数。
完整代码
|
|
反射获取一个对象的步骤
- 获取类的 Class 对象实例
Class clz = Class.forName("serialize.Apple");
serialize.Apple是 可以在运行时加载类,适合在不知道类名时动态加载类
- 根据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
使用 Java反射 来获取类中的一个名为 setPrice
的方法,它的参数类型是 int
。它的作用是在运行时动态获取某个类的特定方法,之后可以使用这个方法进行操作
- 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj= appleConstructor.newInstance();
- 获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
- 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);
获取反射中的Class对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:
第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
|
|
第二种,使用 .class 方法。
这种方法只适合在编译前就知道操作的 Class。
|
|
第三种,使用类对象的 getClass() 方法。
|
|
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。
|
|
第二种:通过 Constructor 对象的 newInstance() 方法
|
|
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
|
|
通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
|
|
输出结果是:
|
|
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
|
|
输出结果是:
|
|
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。