目录
反射概念
JVM基础
Class对象之源:类的加载过程
反射获取Class对象的三种方法
Class对象的三种常用方法
三种常用方法对应的后续调用
用反射来实现命令执行
反射概念
反射(Reflection)是指在程序运行时可以检查、获取和修改类的属性、方法、构造函数等信息,以及在运行时创建对象、调用方法、访问属性等能力。Java 的反射机制提供了一组 API,使得我们可以在运行时动态操作类和对象,而不需要在编译时确定类的类型。
以下是反射机制的主要功能和用途:
- 获取类的信息:可以通过反射获取类的各种信息,如类名、父类、接口、字段、方法等。
- 创建对象:可以通过反射在运行时动态创建对象。
- 调用方法:可以通过反射在运行时调用对象的方法,包括私有方法。
- 访问和修改属性:可以通过反射在运行时访问和修改对象的属性,包括私有属性。
- 动态代理:可以使用反射生成动态代理对象,实现 AOP(面向切面编程)等功能。
- 实现工厂模式和配置文件解析:通过反射可以实现简单工厂模式、工厂方法模式以及解析配置文件来动态加载类。
反射机制的核心类是 java.lang.Class
,它表示一个类的元数据信息。通过 Class
类的实例,可以获取类的各种信息,并对该类进行操作。常用的反射类还包括 java.lang.reflect.Method
、java.lang.reflect.Field
、java.lang.reflect.Constructor
等,它们分别表示类的方法、字段和构造函数。
JVM基础
二进制字节码:类基本信息,常量池,类方法定义,包含了虚拟机指令
类放在方法区,类的实例化对象放在堆,对象调用方法在栈
Class对象之源:类的加载过程
而反射的核心,就是在类的加载这一步里在JVM堆(heap)中创建的Class对象,其指向方法区中的类元数据信息。
在Java虚拟机中,每个加载的类都会对应一个Class对象,这个Class对象保存了该类的结构信息,比如类的字段、方法、构造函数等。而这些类的结构信息实际上是保存在方法区中的。
同个类的多个实例化对象,共有一个Class对象
反射获取Class对象的三种方法
1.使用类名.class语法:在已知具体类的情况下,可以使用类字面常量的方式获取Class对象。例如,获取String类的Class对象可以使用String.class
。
Class<String> stringClass = String.class;
2.调用对象的getClass()方法:对于已经存在的对象,可以通过调用其getClass()
方法获取对应的Class对象
String str = "Hello";
Class<? extends String> stringClass = str.getClass();
3.使用Class.forName()方法:当类的名称在编译时无法确定时,可以使用Class.forName()
方法根据类的全限定名获取Class对象。该方法需要提供类的全限定名作为参数,并抛出ClassNotFoundException异常。
try {
Class<?> stringClass = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class对象的三种常用方法
1.获取类的构造函数: getConstructors()
该方法返回一个包含当前类所有公共构造函数的数组。如果想要获取非公共构造函数,可以使用getDeclaredConstructors()
方法。
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Constructor<?>[] constructors = c.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
}
}
2.获取类的方法: getMethods()
该方法返回一个包含当前类以及其父类中所有公共方法的数组。如果想要获取非公共方法,可以使用getDeclaredMethods()
方法。
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
}
3.获取类的属性: getFields()
该方法返回一个包含当前类以及其父类中所有公共属性的数组。如果想要获取非公共属性,可以使用getDeclaredFields()
方法。
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) {
Class<?> c = String.class;
Field[] fields = c.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
}
}
三种常用方法对应的后续调用
一旦我们通过getConstructors()
,getMethods()
和getFields()
方法获取了类的构造函数、方法和属性数组后,我们就可以通过这些数组中的元素来进一步操作和调用。
1.对构造函数的后续调用方法:
获取构造函数的参数类型:可以使用getParameters()方法获取构造函数的参数信息。
创建对象实例:通过构造函数对象可以使用newInstance(Object... initargs)方法来创建对象实例。
Constructor<?> constructor = constructors[0]; // 假设取第一个构造函数
Class<?>[] parameterTypes = constructor.getParameterTypes(); // 获取构造函数的参数类型
Object instance = constructor.newInstance("example"); // 通过构造函数创建对象实例
2. 对方法的后续调用方法:
调用方法:使用invoke(Object obj, Object... args)方法来调用方法,第一个参数是对象实例(静态方法可以传入null),后续参数是方法的实际参数。
Method method = methods[0]; // 假设取第一个方法
Object result = method.invoke(instance, "arg1", 123); // 调用方法并获取结果
3.对属性的后续调用方法:
获取和设置属性值:通过Field对象可以使用get(Object obj)和set(Object obj, Object value)方法来获取和设置属性值。
Field field = fields[0]; // 假设取第一个属性
Object value = field.get(instance); // 获取属性值
field.set(instance, "new value"); // 设置属性值
用反射来实现命令执行
①java.lang.Runtime
java.lang.Runtime
的构造方法是私有的,但有一个公开方法getRuntime()
来获取Runtime
对象。
(在使用invoke(Object obj, Object... args)
方法调用方法时,第一个参数obj
表示要调用方法的对象实例。如果要调用的方法是静态方法(即不依赖于对象实例),则可以将该参数设置为null
。)
Class clazz = Class.forName("java.lang.Runtime");
Object runTime = clazz.getMethod("getRuntime").invoke(null);
clazz.getMethod("exec", String.class).invoke(runTime, "calc");
也可以获取Runtime
的私有构造器去构造Runtime
对象
Class clazz = Class.forName("java.lang.Runtime");
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(constructor.newInstance(), "calc");
②java.lang.ProcessBuilder
ProcessBuilder.start()
是另外一种命令执行的方式。
public ProcessBuilder(List command)
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));
public ProcessBuilder(String... command) (String...和String[]等价)
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc"}}));
(在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给
ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组 )