JAVA 反射机制
前言:记录的文章全是个人的小理解加上Javasec中的大部分内容,这是做学习笔记
概念
反射的作用:实现任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例(Instance)、调用任意的类方法、修改任意的类成员变量值等
比如之前笔记中的Unsafe,来获取这个对象的时候就用了两种方法,就是通过构造方法和获取属性这两种方法来获取该对象的实例
首先要知道的通过java反射获取到的其实都是对应的类型的Class实例对象,之前也讲到了类的初始化需要ClassLoader对象,这个返回的也是一个对应类型的Class实例对象!
个人理解:java反射应该就是在ClassLoader中加载实现的吧。。。。
获取Class对象
之前讲了一种方法了,就是通过ClassLoader类中的loadClass方法来进行加载,然后返回的一个Class实例对象,比如classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");
还有两种其他的方法:
类名.class // 如:com.anbai.sec.classloader.TestHelloWorld.class // 触发类中的static静态代码块
Class.forName("com.anbai.sec.classloader.TestHelloWorld") // 小提醒,之前写到说这个会触发类中的static静态代码块
如果是获取Class数组的话方式就有点不一样,自己也第一次学,这里就是关于Java类型的描述符方式(泛型自己也不太懂,有时候得学习)
Class<?> doubleArray = Class.forName("[D");//相当于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
那么比如要加载一个Runtime类的实例就有三种方法:
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = java.lang.Runtime.class;
Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
小知识点: 反射调用内部类的时候需要使用$
来代替.
,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello,然后到时候获取的时候就比如Class runtimeClass1 =Class.forName("com.anbai.Test$Hello")
继续讲如果通过反射来实现Runtime类的exec方法的调用,如下代码即可实现:
public class Runcmd {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = java.lang.Runtime.class;
Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
Constructor runtimeConstructor = runtimeClass1.getDeclaredConstructor();
runtimeConstructor.setAccessible(true);
Runtime runtime = (Runtime) runtimeConstructor.newInstance();
Method ExecMethod = runtimeClass1.getMethod("exec", String.class);
String cmd = "whoami";
Process p = (Process) ExecMethod.invoke(runtime, cmd);
InputStream in = p.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //获取字节输出流
byte[] b = new byte[1024];
int a = -1;
// 读取命令执行结果
while ((a = in.read(b)) != -1) { // in读取出来的数据都放到a(这个a是读取到的长度大小,类型为int)中,在写入到baos中
baos.write(b, 0, a); // write方法,流没有自己去学习过,个人理解应该是用一个输出流来接受,写进去的时候就是写进b数组中,
// 偏移 0就是从开头开始,每次写的长度就是a
}
// 输出命令执行结果
System.out.println(baos.toString());
}
}
反射获取一个类中的方法
这里只把这个挑出来讲,因为其实原理都是类似的,并且只有在学习这个的时候自己也遇到了点坑,所以也把它拿出来
自己写一个Person类,如下:
public class Person {
private static String name;
public Person(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String aName){
name = aName;
}
public static void setStaticName(String aName){
name = aName;
}
public static String getStatiName(){
return name;
}
}
这里要实现获取getName方法,需要注意的是自己定义了1个static name的属性,2个返回name属性的方法,但是一个是静态一个不是静态,还有2个是设置name的方法,这个方法需要传一个参数,也是一个静态一个不是静态
这里开始反射方法的学习,如下代码显示,可以看到反射了两个方法,一个方法是静态的一个不是静态的,并且调用方法的时候第一个参数一个是Person的实例,一个为null,这里就说了自己的一个坑!
总结如下:
1、如果底层方法是静态的,则指定的obj参数将被忽略。 它可能为null
2、如果底层方法所需的形式参数的数量为0,则提供的args数组的长度为0或为空。
3、如果底层方法是一个实例方法,它将使用动态方法查找来调用(根据你的参数判断来调用不同的方法)
4、如果底层方法是静态的,则如果尚未初始化该方法,那么声明该方法的类将被初始化
public class PeronTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class Pclass = Person.class;
Constructor pConstruct = Pclass.getDeclaredConstructor();
Object person = (Person)pConstruct.newInstance();
Method setNameMethod = Pclass.getMethod("setName", String.class);
setNameMethod.invoke(person, "not static setName");
System.out.println(Person.name);
Method setStaticNameMethod = Pclass.getMethod("setStaticName", String.class);
setStaticNameMethod.invoke(null, "static setName");
System.out.println(Person.name);
}
}
来一个javasec的总结:
Java反射机制是Java动态性中最为重要的体现,利用反射机制我们可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC、ORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。