什么是java反射呢,先来看Oracle官方的解释:
“Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.” ( 反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全 限制内使用反射的字段、方法和构造函数对其底层对应的对象进行操作。)
通过java反射我们可以在运行的时候获取每一个类型的成员和成员的信息,通过“反射”,我们可以将java这种静态代码加上动态特性。
反射的作用
- 在运行时返回任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法,可以通过反射调用 private 方法
- 在运行时调用任意一个对象的方法
获取类对象
java反射通常操作的是class对象,所以我们需要先获取java的对象,通常获取java对象有4种方法:
- Class.forName
- 类名.class
- obj.getclass()
- classLoader.loadClass()
1.forname方法
当我们需要使用一个类的时候,需要import进行导入才能进行使用,而forname就不需要
forname方法有两种重载
Class<?> forName(String name)
Class<?> forName(String name, **boolean** initialize, ClassLoader loader)
第⼀个参数是类名;第⼆个参数表示是否初始化;第三个参数是 ClassLoader 。
第二个是否初始化参数属于类的初始化,并不会去调用构造函数
类的初始化参考链接:
使用:
Class.forname(java.lang.Runtime)
2.类名.class方法
使用:
类名.class 如 Runtime.class
3.getClass()方法
不同于以上两种方法getClass方法必须要明确具体的类,然后创建对象。
4. getSystemClassLoader().loadClass() ⽅法
使用:
ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
获取类方法
获取某个Class对象的方法集合,有以下4种方法
- getDeclaredMethods()
- getDeclaredMethod()
- getMethods()
- getMethod()
1.getDeclaredMethods()
getDeclaredMethods()方法返回类或接口声明的所有方法,包括:public、protected、private和默认方法,但不包括继承的方法
用法:
Method[] methods = clazz.getDeclaredMethods()
2.getDeclaredMethod()
getDeclaredMethod()方法只能返回一个特定的方法,此方法返回类或接口声明的所有方法,包括:public、protected、private和默认方法,但不包括继承的方法
getDeclaredMethod()方法参数:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
如上我们可以看到getDeclaredMethod有两个参数,第一个参数是要返回的方法,第二个参数是要返回的方法类型的参数
用法:
#以exec方法为例
Method method = name.getDeclaredMethod("exec", String.class);
注释中的代码涉及到Runtime的单例模式,这个问题我们后面说
3.getMethods()
getMethods() ⽅法返回某个类的所有public⽅法,包括其继承类的public⽅法
用法:
Method[] methods = clazz.getMethods()
4.getMethod()
getMethods() 方法只能返回一个特定的方法,此⽅法返回某个类的所有public⽅法,包括其继承类的public⽅法
getMethod()方法参数:
public Method getMethod(String name, Class<?>... parameterTypes)
如上我们可以看到getMethod有两个参数,第一个参数是要返回的方法,第二个参数是要返回的方法类型的参数,因java中存在重载第二个参数是为了解决这个多重载的问题,如不存在重载问题,那么第二个参数可以省略。
用法:
#以exec方法为例
Method method = clazz.getMethod("exec", String.class);
构造任意类对象
我们先看一串代码
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(),"calc.exe");
}
invoke 是执⾏函数的⽅法,newInstance是实例化类对象的⽅法 ,这个方法我们后面会讲这里先用一下,上述代码是一串反射代码通过反射运行exec函数的方法,但是我们运行发现会报错
我们查看报错发现提示是“Main类无法访问带有修饰符“private”的java.lang.Runtime类的成员”
我们看下"java.lang.Runtime"类的源码
newInstance的本质就是调用这个类的的无参构造器,故主要问题出在newInstance调用Runtime类的无参构造器的时候,无法调用从而产生的问题。
newInstance使用不成功的原因:
- 单例模式
- 使用的类没有无参构造器
- 使用的类构造函数是私有的
单例模式
什么是单例模式呢?
例如Runtime类就是一个很典型的单例模式,我们来看Runtime类的代码:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
我们看如上代码可以看到我们的Runtime无参构造器是私有的,但是他实例化成了currentRuntime,然后在getRuntime方法中返回了,那么newInstance遇到单例方法的时候可以调用getRuntime方法,看如下代码:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc");
}
我们将上述代码进行拆解一下
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("exec", String.class);
Method method1 = clazz.getMethod("getRuntime");
Object invoke1 = method1.invoke(clazz);
method.invoke(invoke1, "calc.exe");
}
这里需要说下Object invoke1 = method1.invoke(clazz);这串代码中invoke里面使用的是一个参数对象,为什么不是一个类对象呢,因为getRuntime方法是一个静态方法,invoke第一个参数传入的时候需要注意,如是一个普通方法需要传入类对象,如果是一个静态方法则直接传入一个类就好了,这块儿到后面invoke会详细讲解。
使用的类没有无参构造器
如使用的类没有无参构造器的时候我们应该如何解决呢,我推荐一种解决方式是调用有参的构造器,这里我们需要用到一个新的方法 getConstructor
getConstructor
我们先来看下getConstructor构造器源码
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.PUBLIC);
}
我们发现getConstructor只有一个参数,这个参数是我们要调用的有参构造器的参数类型
我们以ProcessBuilder为例看下列代码:
如对ProcessBuilder不了解的请参考链接:Java进程ProcessBuilder类的介绍及使用,ProcessBuilder调用外部程序执行shell命令Linux命令,示例调用本地FFMPEG命令执行视频转码和剪辑-CSDN博客
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
}
如不了解Arrays.aslist还有ArrayList的参考链接:
如何在 Java 中创建 ArrayList 列表?-阿里云开发者社区
因上述代码惨在一个ProcessBuilder类的强转,我们用原来的方法进行编写
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("clac.exe")));
}
我们看下getConstructor下的newInstance方法的源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
我们发现newInstance方法的传参是传入的有参构造器所传入的参数
使用的类构造函数是私有的
前面我们讲单例方法的时候我们使用getRuntime函数来解决Runtime类构造函数是私有的问题
那么我们还有一个方法,就是用 getDeclaredConstructor()方法来解决
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
Class clazz = Class.forName("java.lang.Runtime");
//获取构造方法
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(declaredConstructor.newInstance(), "calc.exe");
}
setAccessible方法是修改他的作用域。
执行函数的方法
invoke
我们先来看下invoke源码
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
invoke方法有两个参数,第一个参数是Object类,也就是调用该方法的对象,第二个参数是调用的方法所要传的参
需要注意的是第一个参数:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
代码示例:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc");
}
标签:反射,java,invoke,clazz,public,机制,Runtime,方法,Class
From: https://blog.csdn.net/m0_62484818/article/details/143024087