反射是Java的一个重要特性,通过反射可以调用程序运行时任意类、对象的方法,也能访问或修改变量的值,并且能判断对象所属的类。
实例化
弄明白反射还是要稍微深入了解实例化的过程,下面有个样例,取自代码审计星球反射系列下ir0ny师傅的评论。
Person p = new Person(“zhangsan”,20); 实例化Person类,这句话做了什么?
- 因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
- 在堆内存中开辟空间,分配内存地址。
- 在堆内存中建立对象的特有属性。并进行默认初始化。
- 对属性进行显示初始化。
- 对对象进行构造代码块初始化。
- 对对象进行对应的构造函数初始化。
- 将内存地址付给栈内存中的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运行,因此获得了字节码就等于我们获得了程序运行时类的状态。
- forName()
Class.forName("java.lang.Runtime");
forName实现了动态加载类,因此还会执行静态代码块(类中static段的代码)。
- 类名.class
Class rt = Runtime.class;
仅仅是获得类对象。
- getClass()
Runtime run = Runtime.getRuntime();
Class<?> name = run.getClass();
这种要先获取实例化对象在拿到class object。
- getSystemClassLoader().loadClass()
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
仅仅是获得类对象。
获取类方法
- getDeclaredMethods
返回类或接口声明的所有方法,包括public、protected、private和默认方法,但不包括继承的方法。
Class<?> a = Runtime.class;
Method[] declareMethods = a.getDeclaredMethods();
- getMethods
返回某个类的所有public方法,包括其继承类的public方法。
Class a=Runtime.class;
Method[] methods = a.getMethods();
- getMethod
getMethod方法只能返回一个特定的方法。注意,第二个参数是第一个参数方法的参数,因为exec的参数是String,因此后面要跟上String.class
Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();
Method method = name.getMethod("exec",String.class);
- getDeclaredMethod
Class<?> a = Runtime.class;
Method[] declareMethods = a.getDeclaredMethod("exec",String.class);
同上。
获取类的成员变量
四个方法对应上文获取类方法的四个方法。
- getDeclaredFields
Student student = new Student();
Class<?> name = student.getClass();
Field[] getDeclaredFields = name.getDeclaredFields();
- getFields
Student student = new Student();
Class<?> name = student.getClass();
Field[] getFields = name.getFields();
- getField
Student student = new Student();
Class<?> name = student.getClass();
Field getField = name.getField("content");
变量没有参数,因此获取变量自然不需要第二个参数了,注意只能获取public类型的变量。
- 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构造函数则不能实例化。
调用方法
- 直接通过.调用方法,因此要在实例化对象的基础上获取上。
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!");
- 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"}}));