反射相关概念
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行状态的时候才动态加载类,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的方法/访问属性。这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
反射机制流程图(或可直接移步)
class对象获取
对于普通用户我们可以采用以下方法创建实例:
Person test = new Person();
而我们在创建class类的实例对象却不能使用上述方法,运行会抛出错误
Class test = new Class();
因为其构造器是私有的,只有JVM能够创建Class对象
获取办法
1、类的.class属性
第一种就是最简单明了的方式,我们可以通过类名的属性class获取。
Class c1=ReflectDemo.class;
2、实例化对象的getClass()方法
第二种我们可以先实例化一个对象,之后在调用getClass()方法。
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
3、Class.forName(String className):动态加载类
第三种则是调用Class类中的forName方法,将字节码文件加载进内存,返回Class对象。
Class c3 = Class.forName("reflectdemo.ReflectDemo");
注意点
我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。
而使用类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误;
而使用实例化对象的getClass()方法,需要本身创建一个对象,本身就没有了使用反射机制意义。
所以我们在获取class对象中,一般使用Class.forName方法去获取。
获取数组类型的Class对象需要特殊注意,需要使用Java类型的描述符方式,如下:
Class<?> doubleArray = Class.forName("[D");//相当于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
获取Runtime类Class对象代码片段:
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
。
获取成员
获得上一个class对象后,可通过如下方法获得成员变量Field:
Field[] getFields() :获取所有public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getField(String name) 获取指定名称的 public修饰的成员变量
Field getDeclaredField(String name) 获取指定的成员变量
成员方法Method,构造函数Constructor也是类似的
获取到java.lang.reflect.Method
对象以后我们可以通过Method
的invoke
方法来调用类方法。
Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。
获取成员变量值:
Object obj = field.get(类实例对象);
修改成员变量值:
field.set(类实例对象, 修改后的值);
当我们没有修改的成员变量权限时可以使用: field.setAccessible(true)
的方式修改为访问成员变量访问权限。
如果我们需要修改被final
关键字修饰的成员变量,那么我们需要先修改方法
// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");
// 设置modifiers修改权限
modifiers.setAccessible(true);
// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 修改成员变量值
field.set(类实例对象, 修改后的值);
反射创建类对象
一般我们使用Class对象的newInstance()方法来进行创建类对象。
使用的方式也特别简单,只需要通过forname方法获取到的class对象中进行newInstance方法创建即可。
Class c = Class.forName("com.reflect.MethodTest"); // 创建Class对象Object m1 = c.newInstance(); // 创建类对象
一个eg:
import java.lang.reflect.Method;
public class ReflectTest {
public void reflectMethod() {
System.out.println("反射测试成功!!!");
}
public static void main(String[] args) {
try {
Class c = Class.forName("com.reflect.ReflectTest"); // 创建Class对象
Object m = c.newInstance(); // 创建类实例对象
Method method = c.getMethod("reflectMethod"); // 获取reflectMethod方法
method.invoke(m); // 调用类实例对象方法
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意点
public Object invoke(Object obj, Object... args)
invoke方法第一个参数并不是固定的:
如果调用这个方法是普通方法,第一个参数就是类对象;
如果调用这个方法是静态方法,第一个参数就是类;
反射java.Lang.Runtime
java.lang.Runtime
因为有一个exec
方法可以执行本地命令,所以在很多的payload
中我们都能看到反射调用Runtime
类来执行本地系统命令。
反射Runtime执行本地命令代码片段:
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.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"));
为何要设置accessible?
在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。
public class Runtime {
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
从上面的Runtime
类代码注释我们看到它本身是不希望除了其自身的任何人去创建该类实例的,因为这是一个私有的类构造方法,所以我们没办法new
一个Runtime
类实例即不能使用Runtime rt = new Runtime();
的方式创建Runtime
对象,但借助反射机制,修改方法访问权限从而能间接的创建出了Runtime
对象。
runtimeClass1.getDeclaredConstructor
和runtimeClass1.getConstructor
都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组,如:clazz.getDeclaredConstructor(String.class, String.class)
。
如果我们想获取类的所有构造方法可以使用:clazz.getDeclaredConstructors
来获取一个Constructor
数组。
获取到Constructor
以后我们可以通过constructor.newInstance()
来创建类实例,同理如果有参数的情况下我们应该传入对应的参数值,如:constructor.newInstance("admin", "123456")
。当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)
修改访问权限就可以成功的创建出类实例了。
若不执行私有方法?
Runtime.getRuntime().exec(String command)//重载Runtime.exec