在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为 java 的反射机制。
java.lang.Runtime类 主要方法是getRuntime(),Runtime可以调用exec()方法执行命令;
每个java程序中都有一个Runtime实例,这个Runtime实例调用getRuntime方法,返回Runtime对象,这个对象拥有exec执行命令的方法
获取类的对象 forName() 直接获取 getClass getSystemClassLoader.loadClass()方法
构造任意类的对象 无参数的时候使用 className.newInstance() 有参数的时候使用 getConstructor.newInstance()
调用任意实例对象的方法: invoke()
一段利用反射构建的具有动态特性的代码
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
forName 有两个函数重载:
Class.forName(className)
// 等于
Class.forName(className, true, currentLoader)
关键是对 initialize的理解
明确一点类初始化和对象初始化是不同的概念,Class的获取一般与目标类的初始化有关,和目标对象初始化无关
initialize对应的是类的初始化
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
Runtime类就是单例模式,意思是只有类初始化的时候会执行一次构造函数,后面只能通过getInstance()方法 (Runtime里面就是getRuntime)获取这个对象
//这段代码运行会报错,cannot access a member of class java.lang.Runtime (in module java.base) with modifiers "private",原因就是构造方法是私有的,不能访问
Class cls = Class.forName("java.lang.Runtime");
cls.getMethod("exec",String.class).invoke(cls.newInstance(),"calc.exe");
getMethod()方法返回一个类的某个特定的公有方法,需要通过方法名 和 传入的参数来确定某个具体的方法,这里与函数的重载相关;函数的重载就是相同的函数名,但是接受的参数个数和参数类型可以不同,这就是函数的重载
invoke()方法 的作用是执行方法,getMethod()返回的方法想要执行,通过调用invoke方法来实现。普通的方法调用模式 [1].method([2],[3],...),用invoke表现为 method.invoke([1],[2],[3],....),其中这个[1]就是实例化的对象
前面提到 Runtime类只在初始化的时候执行构造函数得到实例化的对象,后面要想在得到 Runtime 的实例化对象不能通过 newInstance()的方法得到,而是要通过 getRuntime()的方法得到一个Runtime的实例化对象,然后调用exec方法
如果一个类中没有无参构造方法,或者没有单例模式的静态方法,那么需要通过新的构造方法 getConstructor来做,因为构造方法也有可能重载,所以需要传递参数类型给 getConstructor,这个参数的类型根据构造方法的参数类型确定
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"}}));
第二种涉及到变长参数,可以看到第二种的代码里面有一个二维数组,这是因为newInstance()和ProcessBuilder()都是接受变长参数,两个数组相加得到二维数组
利用完全反射的方式编写代码,所有操作通过Class对象和Method对象实现
Class clazz = Class.forName("java.lang.ProcessBuilder");
Method getConstructorMethod = clazz.getClass().getMethod("getConstructor",new Class[]{Class[].class});
Object b = getConstructorMethod.invoke(clazz,new Object[]{new Class[]{String[].class}});
Method newInstanceMethod = b.getClass().getMethod("newInstance",new Class[]{Object[].class});
Object d = newInstanceMethod.invoke(b,new Object[]{new String[][]{{"calc.exe"}}});
Method startMethod = d.getClass().getMethod("start",new Class[]{});
startMethod.invoke(d,new Object[]{});