反射是Java的一个重要特性,通过反射可以调用程序运行时任意类、对象的方法,也能访问或修改变量的值,并且能判断对象所属的类。

实例化

弄明白反射还是要稍微深入了解实例化的过程,下面有个样例,取自代码审计星球反射系列下ir0ny师傅的评论。

Person p = new Person(“zhangsan”,20); 实例化Person类,这句话做了什么?

  1. 因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
  2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
  3. 在堆内存中开辟空间,分配内存地址。
  4. 在堆内存中建立对象的特有属性。并进行默认初始化。
  5. 对属性进行显示初始化。
  6. 对对象进行构造代码块初始化。
  7. 对对象进行对应的构造函数初始化。
  8. 将内存地址付给栈内存中的p变量。

实例化TrainPrint,下面的代码将输出什么?getClass以及class就先理解为java文件编译后的class文件好了,代码改自Java安全漫谈。

public class TrainPrint {
    {
        System.out.printf("Empty block initial %s\n", this.getClass());
    }
    static {
        System.out.printf("Static initial %s\n", TrainPrint.class);
    }
    public void fuck(){
        System.out.println(this);
    }
    public TrainPrint() {
        System.out.printf("Initial %s\n", this.getClass());
        this.fuck();
    }
}
--------------------------------------------------------------------------------
public static void main(String[] args){
        TrainPrint trainPrint=new TrainPrint();
    }
----------------------------------output----------------------------------------
Static initial class com.ms08067.TrainPrint
Empty block initial class com.ms08067.TrainPrint
Initial class com.ms08067.TrainPrint
com.ms08067.TrainPrint@4b67cf4d

可以看到最先执行的就是静态代码块(注意这里用不了this,因为this指代当前TrainPrint这个类的对象,现在类刚刚加载,还没有分配内存地址),随后执行代码块,然后执行构造方法,然后调用fuck方法输出this。

获取类对象

我的理解:类对象就是java文件编译后的class文件,字节码存放于class文件中,之后交给JVM运行,因此获得了字节码就等于我们获得了程序运行时类的状态。

  1. forName()
Class.forName("java.lang.Runtime");

forName实现了动态加载类,因此还会执行静态代码块(类中static段的代码)。

  1. 类名.class
Class rt = Runtime.class;

仅仅是获得类对象。

  1. getClass()
Runtime run = Runtime.getRuntime();
Class<?> name = run.getClass();

这种要先获取实例化对象在拿到class object。

  1. getSystemClassLoader().loadClass()
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");

仅仅是获得类对象。

获取类方法

  1. getDeclaredMethods

返回类或接口声明的所有方法,包括public、protected、private和默认方法,但不包括继承的方法。

Class<?> a = Runtime.class;
Method[] declareMethods = a.getDeclaredMethods();
  1. getMethods

返回某个类的所有public方法,包括其继承类的public方法。

Class a=Runtime.class;
Method[] methods = a.getMethods();
  1. getMethod

getMethod方法只能返回一个特定的方法。注意,第二个参数是第一个参数方法的参数,因为exec的参数是String,因此后面要跟上String.class

Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();
Method method = name.getMethod("exec",String.class);
  1. getDeclaredMethod
Class<?> a = Runtime.class;
Method[] declareMethods = a.getDeclaredMethod("exec",String.class);

同上。

获取类的成员变量

四个方法对应上文获取类方法的四个方法。

  1. getDeclaredFields
Student student = new Student();
Class<?> name = student.getClass();
Field[] getDeclaredFields = name.getDeclaredFields();
  1. getFields
Student student = new Student();
Class<?> name = student.getClass();
Field[] getFields = name.getFields();
  1. getField
Student student = new Student();
Class<?> name = student.getClass();
Field getField = name.getField("content");

变量没有参数,因此获取变量自然不需要第二个参数了,注意只能获取public类型的变量。

  1. getDeclareField
Student student = new Student();
Class<?> name = student.getClass();
Field getDeclaredField = name.getDeclaredField("name");

获取对象

获取对象都是用newInstance()方法,注意要先获得类对象。

Class<?> cls = Class.forName("com.ms08067.newInstance.newInstanceExample");
        // 无参数
System.out.println("无参数构造对象第一种方法:");
newInstanceExample no_parameters_1 = (newInstanceExample)cls.newInstance();
System.out.println("无参数构造对象第二种方法");
newInstanceExample no_parameters_2 = newInstanceExample.class.newInstance();
// 有参数
System.out.println("有参数构造对象第一种方法");
newInstanceExample have_parameters_1 = (newInstanceExample)cls.getConstructor(String.class).newInstance("yes");
System.out.println("有参数构造对象第二种方法");
newInstanceExample have_parameters_2 = newInstanceExample.class.getConstructor(String.class).newInstance("yes");

如果构造函数有参数,那么需要调用getConstructor()并且传入参数对应类型的类对象。

但这个方法有限制:

如果有private构造函数则不能实例化。

调用方法

  1. 直接通过.调用方法,因此要在实例化对象的基础上获取上。
Class<?> cls = Class.forName("com.ms08067.newInstance.newInstanceExample");
newInstanceExample no_parameters = newInstanceExample.class.newInstance();
newInstanceExample have_parameters = newInstanceExample.class.getConstructor(String.class).newInstance("yes");
// 调用对象的方法
// 直接调用
no_parameters.method_1();
no_parameters.method_2("no!");
have_parameters.method_1();
have_parameters.method_2("yes!");
  1. invoke调用,因此要在获得类对象的基础上获取方法。
Class<?> cls = Class.forName("com.ms08067.newInstance.newInstanceExample");
newInstanceExample no_parameters = newInstanceExample.class.newInstance();
Object method = cls.getMethod("method_2", String.class).invoke(no_parameters,"invoke方法");
System.out.println(method);
String obj = (String) cls.getDeclaredMethod("method_1").invoke(no_parameters);
System.out.println(obj);

执行系统命令

在学会了invoke调用,接着来学习通过反射执行系统命令。

Runtime

Runtime这个类有exec可以调用系统命令,因此我们可以加载Runtime这个类。

读一下Runtime源码,就会发现Runtime的构造方法是静态的,因此无法通过newInstance实例化Runtime,可以自己去试一下,new Runtime会报错。而正确的获得实例化对象的方法是Runtime.getRuntime()【getRuntime方法是静态的】

因此,第一种方法

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

clazz.getMethod(“getRuntime”).invoke(clazz)通过这段代码调用getRuntime获得对象,然后传进invoke里进而执行exec函数。

如果不调用getRuntime,也可以通过getDeclaredConstructor并且设置作用域执行,第二种方法

Class clazz = Class.forName("java.lang.Runtime"); 
Constructor m = clazz.getDeclaredConstructor(); 
m.setAccessible(true); 
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
ProcessBuilder

ProcessBuilder这个类的构造方法是public的,一个支持参数为LIst,另一个支持String。并且它的start方法能够执行命令,因此也可以通过ProcessBuilder执行命令。

第一种方法:

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance( Arrays.asList("calc.exe")));

第二种方法:

Class clazz = Class.forName("java.lang.ProcessBuilder");     clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));