Featured image of post Java反射

Java反射

java反射知识

使用场景

反序列化通常就是通过一个类的函数来调用另一个类的函数,从而构成链子

像一般调用函数,最基本的肯定是

1
2
person person = new person(***,***);有参就加值,无参就不加值
person.toString();来调用person类的toString方法

但是如果类的构造函数是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()

当然这种实例化只能调用无参构造函数实例化。可以先获取构造器,再调用有参构造函数实例化,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
person类
public class person {
   public String name;
    public int age;

    private person(String name,int age)
    {
        this.name = name;
        this.age = age;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    public String toString() {
        return "person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
1
2
3
4
5
Class c = person.class;
Constructor personconstructor =  c.getDeclaredConstructor(String.class,int.class);
personconstructor.setAccessible(true);
person p = (person) personconstructor.newInstance("abc",21);
System.out.println(p.toString());

image-20250106160846331

  • **getDeclaredConstructor(String.class,int.class)**是获取私有构造函数,参数要对应着构造函数的参数
  • **personconstructor.setAccessible(true);**构造函数是私有的一定要加上
  • 如果toString函数是私有的,**p.toString()**还是会报错

而要能调用私有函数就需要Field和Method

Field设置属性值

1
2
3
4
5
6
Class pclass = person.class;
Field field = pclass.getField("age"); age是person的属性
field.set(p,20); 
//p是上面通过构造器实例化的person对象,基于person构造函数是私有的情况下
//如果构造函数不是私有的,那么可以直接new person实例化,就不需要构造器了
System.out.println(p.toString());

image-20250106170052415

如果属性是私有的就需要以上方法加declared获取私有内容,如getDecalredFields()获取私有属性

使用setAccessible(true)允许修改私有变量,私有方法等private内容

1
2
pclass.getDeclaredField("name");
personconstructor.setAccessible(true);

Method调用函数

假如这里修改一下person类的toString函数

1
2
3
4
5
6
private String toString(String sex) {
        return "person{" +
                "name='" + name + '\'' +
                ", age=" + age + ", sex='"+sex+"'"+
                '}';
    }

变私有了,而且增加了参数

1
2
3
4
5
6
7
Class pclass = person.class;
Method method = pclass.getDeclaredMethod("toString",String.class);
//获取函数名叫toString的函数,String.class是函数的参数
method.setAccessible(true);
//同样要设置为true,和上面一样
System.out.println(method.invoke(p,"man"));
//invoke调用,第一个参数是person的实例对象,第二个就是给参数传参

image-20250106180243960

反射利用

反序列化需要从能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(方法名,该方法参数类型)

1
2
3
4
Runtime r = Runtime.getRuntime();
Class<?> c = Runtime.class;
Method m = c.getMethod("exec", String.class);
m.invoke(r,"calc");

getRuntime()是不是也能顺便改成反射了呢?

反射操作

1
2
3
4
5
6
//代码1
Class<Runtime> c = Runtime.class;
Method gMethod = c.getMethod("getRuntime",null);//getRuntime无参,即null
Object r = gMethod.invoke(null,null);//执行getRuntime,静态方法无需实例,第一个参数为null;
Method execMethod = c.getMethod("exec", String.class);//exec参数类型为string
execMethod.invoke(r,"calc");

正向操作

1
2
Apple apple = new Apple();
apple.setPrice(4);

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射 API 进行反射调用

1
Class clz = Class.forName("com.chenshuyi.reflect.Apple");
  • 这行代码通过Class.forName()方法动态加载名为com.chenshuyi.reflect.Apple的类。注意,类的全限定名(包括包名)必须正确,才能成功加载。返回值clzApple类的Class对象,它包含了关于这个类的所有元信息。
1
Method method = clz.getMethod("setPrice", int.class);
  • 通过clz.getMethod()方法,获取Apple类中的setPrice方法。getMethod()需要传递方法名称以及该方法的参数类型,在这里,setPrice方法接受一个int类型的参数。因此,int.class表示参数的类型。
1
Constructor constructor = clz.getConstructor();
  • 获取Apple类的无参构造函数(默认构造函数)。getConstructor()方法用于获取类的构造方法,若不传参数,则获取无参构造器。
1
Object object = constructor.newInstance();
  • 使用上面获取的无参构造函数,创建Apple类的实例对象。newInstance()方法用于调用构造器并创建实例,返回值为Object类型。
1
method.invoke(object, 4);
  • 通过Method对象method调用反射的setPrice方法,并传入参数4invoke()方法的第一个参数是目标对象(即Apple类的实例object),后面是要传递给方法的参数。

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package serialize;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("serialize.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

反射获取一个对象的步骤

  • 获取类的 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 类对象。

1
Class clz = Class.forName("java.lang.String");

第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

1
Class clz = String.class;

第三种,使用类对象的 getClass() 方法。

1
2
String str = new String("Hello");
Class clz = str.getClass();

通过反射创建类对象

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

1
2
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

1
2
3
Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

1
2
3
Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

通过反射获取类属性、方法、构造器

我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

1
2
3
4
5
Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

1
price

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

1
2
3
4
5
Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

1
2
name
price

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

最后更新于 Mar 03, 2025 07:35 UTC
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计