Java反射机制

137 阅读10分钟

Java反射简介

Java 的反射(Reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。

—— JAVA 反射机制 - 百度百科

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

比如如下代码:

Apple apple = new Apple();

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象。通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法)
  • 在运行时调用任意一个对象的方法

反射的使用

反射相关的类一般都在java.lang.relfect包里。

获得 Class 对象

一般有三种方法:

  • 使用 Class 类的 forName 静态方法

    当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

    Class clz = Class.forName("java.lang.String");
    
  • 使用 .class 方法

    Class clz = String.class;
    
  • 使用类对象的getClass方法

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

获取数组类型的 Class 对象需要特殊注意,需要使用 Java 类型的描述符方式,如下:

Class<?> doubleArray = Class.forName("[D");//相当于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class

判断是否为某个类的实例

通常我们用 instanceof 关键字来判断是否为某个类的实例,同时我们也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法。

public native boolean isInstance(Object obj);

创建类实例

通过反射来创建对象主要有两种方式:

  • 通过 Class 对象的newInstance方法

    Class clz = Apple.class;
    Apple apple = (Apple)clz.newInstance();
    
  • 通过 Constructor 对象的newInstance方法

    先通过Class对象获取指定的 Constructor 对象,再调用 Constructor 对象的newInstance方法来创建实例,这种方法可以用指定的构造器构造类的实例。

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

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

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

获取类属性、方法、构造器

我们先自定义一个作为实验对象的类:

package fun.h4ck;

class Calc {
    public int a;
    public int b;
    private String token = "right_token";
    public final int final_f = 1;

    public Calc() {
        this.a = 3;
        this.b = 4;
        System.out.println("无参构造函数:");
        System.out.printf("a: %d\nb: %d\n", this.a, this.b);
        System.out.println("===========================================================");
    }

    private Calc(int a, int b) {
        this.a = a;
        this.b = b;
        System.out.println("(int, int)参数构造函数:");
        System.out.printf("a: %d\nb: %d\n", this.a, this.b);
        System.out.println("===========================================================");
    }

    public Calc(int a, int b, String token) {
        if (this.token.equals(token)) {
            this.a = a;
            this.b = b;
            System.out.println("(int, int, String)参数构造函数:");
            System.out.printf("a: %d\nb: %d\ntoken: %s\n", this.a, this.b, this.token);
            System.out.println("===========================================================");
        }
    }

    public int add(int a, int b) {
        return a + b;
    }
    public int sub(int a, int b) {
        return a - b;
    }

    private String getToken() {
        return this.token;
    }
}

获取某个 Class 对象的方法,主要有以下几种方法:

  • getMethods 方法返回某个类的所有公有(public)方法,包括其继承类的公有方法,可以获取到父类的方法。

    public Method[] getMethods() throws SecurityException
    
  • getDeclaredMethods 方法返回类或接口声明的所有方法,包括公有、保护、默认(包)访问和私有方法,但不包括继承的方法。

    public Method[] getDeclaredMethods() throws SecurityException
    
  • getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

    public Method getMethod(String name, Class<?>... parameterTypes)
    
  • getDeclaredMethod方法返回一个特定的方法,包括公有、保护、默认(包)访问和私有方法,但不包括继承的方法。

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    

我们用代码来表示:

    /**
     * getMethod 反射获取类中的方法。
     */
    public void getMethod() throws InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // getMethod 获取类中的公有方法。
        Method method = clz.getMethod("add", int.class, int.class);
        System.out.printf("getMethod 获取的方法:\n%s\n", method);

        System.out.println("===========================================================");

        // getDeclaredMethod 获取类中的公有、保护、默认(包)访问和私有方法,但不包括继承的方法。
        Method declaredMethod = clz.getDeclaredMethod("getToken");
        System.out.printf("getDeclaredMethod 获取的方法::\n%s\n", declaredMethod);

        System.out.println("===========================================================");

        // getMethods 方法获取类中的所有公有方法。
        System.out.println("getMethods 获取的方法:");
        Method[] methods = clz.getMethods();
        for(Method m:methods) {
            System.out.println(m);
        }

        System.out.println("===========================================================");

        // getDeclaredMethods 方法获取的所有公有、保护、默认(包)访问和私有方法,但不包括继承的方法。
        System.out.println("getDeclaredMethods 获取的方法:");
        Method[] declaredMethods = clz.getDeclaredMethods();
        for(Method m:declaredMethods) {
            System.out.println(m);
        }

        System.out.println("===========================================================");
    }

输出结果为:

无参构造函数:
a: 3
b: 4
===========================================================
getMethod 获取的方法:
public int fun.h4ck.Calc.add(int,int)
===========================================================
getDeclaredMethod 获取的方法::
private java.lang.String fun.h4ck.Calc.getToken()
===========================================================
getMethods 获取的方法:
public int fun.h4ck.Calc.sub(int,int)
public int fun.h4ck.Calc.add(int,int)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
===========================================================
getDeclaredMethods 获取的方法:
public int fun.h4ck.Calc.sub(int,int)
private java.lang.String fun.h4ck.Calc.getToken()
public int fun.h4ck.Calc.add(int,int)
===========================================================

获取某个 Class 对象的属性,主要有getFieldgetDeclaredFieldgetFieldsgetDeclaredFields几种方法,与上面获取方法的区别相同,这里不再详述。

代码:

    /**
     * getField 反射获取类中的属性。
     */
    public void getFiled() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // getField 获取类中的公有字段。
        System.out.println("getField 获取的字段:");
        Field field = clz.getField("a");
        System.out.println(field);

        System.out.println("===========================================================");

        // getDeclaredField 获取类中的公有、保护、默认(包)访问和私有字段,但不包括继承的字段。
        System.out.println("getDeclaredField 获取的字段:");
        Field declaredField = clz.getDeclaredField("token");
        System.out.println(declaredField);

        System.out.println("===========================================================");

        // getFields 方法获取类中的所有公有字段。
        System.out.println("getFields 获取的字段:");
        Field[] fields = clz.getFields();
        for(Field f:fields) {
            System.out.println(f);
        }

        System.out.println("===========================================================");

        // getDeclaredFields 方法获取的所有公有、保护、默认(包)访问和私有字段,但不包括继承的字段。
        System.out.println("getDeclaredFields 获取的字段:");
        Field[] declaredFields = clz.getDeclaredFields();
        for(Field f:declaredFields) {
            System.out.println(f);
        }

        System.out.println("===========================================================");
    }

输出结果为:

无参构造函数:
a: 3
b: 4
===========================================================
getField 获取的字段:
public int fun.h4ck.Calc.a
===========================================================
getDeclaredField 获取的字段:
private java.lang.String fun.h4ck.Calc.token
===========================================================
getFields 获取的字段:
public int fun.h4ck.Calc.a
public int fun.h4ck.Calc.b
===========================================================
getDeclaredFields 获取的字段:
public int fun.h4ck.Calc.a
public int fun.h4ck.Calc.b
private java.lang.String fun.h4ck.Calc.token
===========================================================

获取某个 Class 对象的构造函数,同样的,有getConstructorgetDeclaredConstructorgetConstructorsgetDeclaredConstructors几种方法,区别也是类似的。

构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如clz.getConstructor(int.class, int.class, String.class);

直接看代码:

    /**
     * getConstructor 反射获取类中的构造方法。
     */
    public void getConstructor() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // getConstructor 获取类中的公有构造方法。
        Constructor<?> constructor = clz.getConstructor(int.class, int.class, String.class);
        System.out.println("getConstructor 获取的构造方法:");
        System.out.println(constructor);

        System.out.println("===========================================================");

        // getDeclaredConstructor 获取类中的公有、保护、默认(包)访问和私有构造方法。
        Constructor<?> declaredConstructor = clz.getDeclaredConstructor(int.class, int.class);
        System.out.println("getDeclaredConstructor 获取的构造方法:");
        System.out.println(declaredConstructor);

        System.out.println("===========================================================");

        // getConstructors 方法获取类中的所有公有构造方法。
        System.out.println("getConstructors 获取的构造方法:");
        Constructor<?>[] constructors = clz.getConstructors();
        for (Constructor<?> c : constructors) {
            System.out.println(c);
        }

        System.out.println("===========================================================");

        // getDeclaredConstructors 方法获取类中的所有公有、保护、默认(包)访问和私有构造方法。
        System.out.println("getDeclaredConstructors 获取的构造方法:");
        Constructor<?>[] declaredConstructors = clz.getDeclaredConstructors();
        for (Constructor<?> c : declaredConstructors) {
            System.out.println(c);
        }

        System.out.println("===========================================================");
    }

结果为:

无参构造函数:
a: 3
b: 4
===========================================================
getConstructor 获取的构造方法:
public fun.h4ck.Calc(int,int,java.lang.String)
===========================================================
getDeclaredConstructor 获取的构造方法:
private fun.h4ck.Calc(int,int)
===========================================================
getConstructors 获取的构造方法:
public fun.h4ck.Calc()
public fun.h4ck.Calc(int,int,java.lang.String)
===========================================================
getDeclaredConstructors 获取的构造方法:
public fun.h4ck.Calc()
public fun.h4ck.Calc(int,int,java.lang.String)
private fun.h4ck.Calc(int,int)
===========================================================

调用或修改获取到的属性、方法、构造器

我们先来看如何调用由反射获取到的类中的方法。

当我们使用反射从类中获取到一个方法后,就可以使用invoke方法来调用这个方法,这里需要注意的是,如果获取到的方法是非公有方法,不能直接调用,需要做如下修改:

declaredMethod.setAccessible(true);

然后再使用invoke去调用。

method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在 Java 中调用静态方法是不需要有类实例的,可以直接类名.方法名(参数)的方式调用。

method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型

具体细节直接看代码:

    /**
     * invokeMethod 反射调用类中的方法。
     */
    public void invokeMethod() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // invokeMethod 调用公有 add 方法。
        Method method = clz.getMethod("add", int.class, int.class);
        int result = (int) method.invoke(object, 10, 20);
        System.out.printf("invokeMethod:\n10 + 20 = %s\n", result);

        System.out.println("===========================================================");

        // invokeDeclaredMethod 调用私有 getToken 方法。
        Method declaredMethod = clz.getDeclaredMethod("getToken");
        declaredMethod.setAccessible(true);
        String result1 = (String) declaredMethod.invoke(object);
        System.out.printf("invokeDeclaredMethod:\ntoken = %s\n", result1);

        System.out.println("===========================================================");
    }

如果没有setAccessible(true)操作,会报java.lang.IllegalAccessException错误。

正常执行结果:

无参构造函数:
a: 3
b: 4
===========================================================
invokeMethod:
10 + 20 = 30
===========================================================
invokeDeclaredMethod:
token = right_token
===========================================================

我们再来看看如何修改类的属性,与执行方法是相类似的,非公有方法需要setAccessible(true)操作,如果我们需要修改被final关键字修饰的成员变量,那么我们需要先修改 modifiers 方法。

看代码:

    /**
     * setField 反射设置类中的属性。
     */
    public void setFiled() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // setField 设置类中的公有字段。
        Field field = clz.getField("a");
        field.set(object, 100);
        System.out.printf("setField:\na = %s\n", field.get(object));

        System.out.println("===========================================================");

        // setDeclaredField 设置类中的公有、保护、默认(包)访问和私有字段,但不包括继承的字段。
        Field declaredField = clz.getDeclaredField("token");
        declaredField.setAccessible(true);
        declaredField.set(object, "fake_token");
        System.out.printf("setDeclaredField:\ntoken = %s\n", declaredField.get(object));

        System.out.println("===========================================================");

        // setFinalField 设置类中的静态常量。
        Field finalField = clz.getField("final_f");

        // 修改 modifiers 方法。
        Field modifiers = finalField.getClass().getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(finalField, finalField.getModifiers() & ~Modifier.FINAL);

        finalField.set(object, 999);
        System.out.printf("setFinalField:\nnew final_f = %s\n", finalField.get(object));

        System.out.println("===========================================================");
    }

结果为:

无参构造函数:
a: 3
b: 4
===========================================================
setField:
a = 100
===========================================================
setDeclaredField:
token = fake_token
===========================================================
setFinalField:
new final_f = 999
===========================================================

我们还可以使用反射调用类中指定的构造函数,对应的方法为newInstance

代码:

    /**
     * newInstance 反射调用类中的构造方法。
     */
    public void newInstance() throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        Class<?> clz = Class.forName("fun.h4ck.Calc");
        Object object = clz.newInstance();

        // newInstance 调用类中的公有构造方法。
        Constructor<?> constructor = clz.getConstructor(int.class, int.class, String.class);
        System.out.println("newInstance 调用的 public 构造方法:");
        System.out.println(constructor.newInstance(55, 66, "right_token"));

        System.out.println("===========================================================");

        // newInstance 调用类中的公有、保护、默认(包)访问和私有构造方法。
        Constructor<?> declaredConstructor = clz.getDeclaredConstructor(int.class, int.class);
        declaredConstructor.setAccessible(true);
        System.out.println("newInstance 调用的 private 构造方法:");
        System.out.println(declaredConstructor.newInstance(77, 88));

        System.out.println("===========================================================");
    }

结果:

无参构造函数:
a: 3
b: 4
===========================================================
newInstance 调用的 public 构造方法:
(int, int, String)参数构造函数:
a: 55
b: 66
token: right_token
===========================================================
fun.h4ck.Calc@4dc63996
===========================================================
newInstance 调用的 private 构造方法:
(int, int)参数构造函数:
a: 77
b: 88
===========================================================
fun.h4ck.Calc@d716361
===========================================================

反射调用 java.lang.Runtime

首先我们正常使用Runtime.exec方法执行系统命令如下:

    /**
     * evilCommand 直接执行系统命令。
     */
    public void evilCommand(String command) throws IOException {
        Runtime runtime = Runtime.getRuntime();
        InputStream result = runtime.exec(command).getInputStream();
        echoResult(result);
    }

其中echoResult方法为我封装好的输出。

然后我们使用前文介绍过的一系列方法来一步步反射调用Runtime.exec方法:

    /**
     * reflectEvilCommand 反射执行系统命令。
     */
    public void reflectEvilCommand(String command) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException, ClassNotFoundException {
        // Class.forName 反射加载 java.lang.Runtime 类。
        Class runtimeClass1 = Class.forName("java.lang.Runtime");
        // getDeclaredConstructor 获取 Runtime 类的所有构造方法。
        Constructor constructor = runtimeClass1.getDeclaredConstructor();
        // 设置无参构造函数为可访问
        constructor.setAccessible(true);
        // newInstance 创建 Runtime 类实例。
        Object runtimeInstance = constructor.newInstance();
        // getMethod 获取 Runtime.exec 方法。
        Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
        // invoke 调用 Runtime.exec 方法。
        Process process = (Process) runtimeMethod.invoke(runtimeInstance, command);
        // echoResult 输出命令执行结果。
        InputStream result = process.getInputStream();
        echoResult(result);
    }

因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改构造方法的访问权限。

本文使用的源码可以关注公众号并回复【Java反射源码】获取

Reference

baike.baidu.com/item/JAVA%E…

www.cnblogs.com/chanshuyi/p…

欢迎关注公众号,获取最新安全文章

Java安全从0到1 - h4ck fun