0x01 Java反射

Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。

​ - 摘抄知识盒子园长代码审计系列

什么是反射?

Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法,本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

反射的原理

如下图

1、创建一个·Person类,javac会将这个这个类编译成class文件。

2、再通过ClassLoader类加载器将class文件中的内容封装到Class类对象中。

3、当new person时,从Class封装类中找到PersonClass并创建。

image-20220104090710125

0x02 获取Class对象

获取Class对象的三种方法

  • Class.forName(“全类名”) - 将字节码文件加载进内存,返回class对象
    • 多用于配置文件,将类名定义在配置文件中
  • 类名.class - 通过类名的属性class获取
    • 多用于参数的传递
  • 对象.getClass() - getClass()方法在Object类中定义
    • 多用于对象的获取字节码的方式

同一个字节码文件在一次程序运行过程中,只会被加载一次,不论通过哪一种方法获取的Class对象都是同一个

FanDemo

package com.time.fanshe;

public class FanDemo {
public static void main(String[] args) throws ClassNotFoundException{

//1.Class.forName("全类名") 将字节码文件加载进内存,返回Class对象
Class<?> c1 = Class.forName("com.time.fanshe.Person");
System.out.println(c1);


//2.通过类名的属性Class获取
Class c2 = Person.class;
System.out.println(c2);

//3.object类中的getClass方法
Person p = new Person();
Class<? extends Person> c3 = p.getClass();
System.out.println(c3);

}
}

Person

package com.time.fanshe;

public class Person {
private String name;
public int age;
}

获取Class对象的成员变量

  • Field[] getFields() :获取所有public修饰的成员变量
    • Field getField(String name) 获取指定名称的 public修饰的成员变量
  • Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
    • Field getDeclaredField(String name)
忽略访问权限修饰符的安全检查
    - setAccessible(true):暴力反射

获取Class对象的构造方法

  • Constructor<?>[] getConstructors()
    • Constructor getConstructor(类<?>… parameterTypes)
  • Constructor getDeclaredConstructor(类<?>… parameterTypes)
    • Constructor<?>[] getDeclaredConstructors()

获取Class对象的成员方法

  • Method[] getMethods()
    • Method getMethod(String name, 类<?>… parameterTypes)
  • Method[] getDeclaredMethods()
    • Method getDeclaredMethod(String name, 类<?>… parameterTypes)

Demo

package com.time.fanshe;

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

public class FanDemo1 {
public static void main(String[] args) throws Exception{


//获取Person类对象
Class<?> c1 = Class.forName("com.time.fanshe.Person");

//获取构造方法
Constructor<?> c2 = c1.getDeclaredConstructor();
c2.setAccessible(true);//暴力反射

Object o1 = c2.newInstance();//创建类对象

//获取当前类所有的成员方法
Method[] method = c1.getDeclaredMethods();
//获取类的指定成员方法
Method method1 = c1.getDeclaredMethod("setName", String.class);

//获取当前类所有成员变量
Field[] fields = c1.getDeclaredFields();

//获取当前类指定的成员变量
Field f1 = c1.getDeclaredField("name");
Field f2 = c1.getDeclaredField("stuend");
f1.setAccessible(true);//获取权限
f1.set(o1,"JT0721");
f2.setAccessible(true);//获取权限
f2.set(o1,"Test");
System.out.println(o1);

//反射调用方法
Object invoke = method1.invoke(o1, "Time");
System.out.println(invoke);
}
}

0x03 反射java.lang.Runtime

测试代码

package com.time.fanshe;

import org.apache.commons.io.IOUtils;

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

public class RunDemo {
public static void main(String[] args) throws Exception {
String cmd = "ls";
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");

// 获取Runtime类对象中的构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);//暴力反射

// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();//创建类

// 获取Runtime类中的的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);

// 调用exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);//调用执行

// 获取命令执行结果
InputStream in = process.getInputStream();//获取结果集

// 输出命令执行结果
System.out.println(IOUtils.toString(in, "UTF-8"));//以UTF-8编码输出
}
}

运行结果

image-20220104122703659

获取 java.lang.Runtime 反射类的片段

String className     = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = java.lang.Runtime.class;
Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);

通过以上任意一种方式就可以获取java.lang.Runtime类的Class对象了,反射调用内部类的时候需要使用$来代替.,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello

反射调用Runtime实现本地命令执行的流程如下:

  1. 反射获取Runtime类对象(Class.forName("java.lang.Runtime"))。
  2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
  3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。
  4. 调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

上面的代码每一步都写了非常清晰的注释,接下来我们将进一步深入的了解下每一步具体含义。