反射和类加载

发布时间 2023-03-27 22:04:24作者: V3g3t4ble

ClassLoader(类加载机制)

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的native方法(defineClass0/1/2)来定义一个java.lang.Class实例。

Java类

Java是编译型语言,我们编写的java文件需要编译成后class文件后才能够被JVM运行

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

JVM在执行TestHelloWorld之前会先解析class二进制内容,JVM执行的其实就是如上javap命令生成的字节码。
image

ClassLoader

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)App ClassLoader(系统类加载器)AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader

值得注意的是某些时候我们获取一个类的类加载器时候可能会返回一个null值,如:java.io.File.class.getClassLoader()将返回一个null对象,因为java.io.File类在JVM初始化的时候会被Bootstrap ClassLoader(引导类加载器)加载(该类加载器实现于JVM层,采用C++编写),我们在尝试获取被Bootstrap ClassLoader类加载器所加载的类的ClassLoader时候都会返回null。

ClassLoader类有如下核心方法:

  • loadClass(加载指定的Java类)
  • findClass(查找指定的Java类)
  • findLoadedClass(查找JVM已经加载过的类)
  • defineClass(定义一个Java类)
  • resolveClass(链接指定的Java类)

Java类动态加载方式

编写一个危险类Shell

package test;

import java.io.IOException;

public class Shell {
    public static void calc() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}
//forName加载
Class c=Class.forName("test.Shell");
c.getMethod("calc").invoke(c.newInstance());
//ClassLoader加载
Class c= ClassLoader.getSystemClassLoader().loadClass("test.Shell");
c.getMethod("calc").invoke(c.newInstance());

Java反射机制

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

获取Class对象

Java反射操作的是java.lang.Class对象,所以我们需要先想办法获取到Class对象,通常我们有如下几种方式获取一个类的Class对象:

System.out.println(Shell.class);//类名.class
Shell s=new Shell();
System.out.println(s.getClass());//实例化对象.getClass()
System.out.println(Class.forName("test.Shell"));//forName
System.out.println(ClassLoader.getSystemClassLoader().loadClass("test.Shell"));//ClassLoader

运行结果:

class test.Shell
class test.Shell
class test.Shell
class test.Shell

反射调用内部类的时候需要使用$来代替
修改Shell类

package test;

import java.io.IOException;

public class Shell {
    public static void calc() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    static class shell{
        public static void cmd() throws IOException {
            Runtime.getRuntime().exec("cmd");
        }
    }
}
Class c=Class.forName("test.Shell$shell");
System.out.println(c);

运行结果:class test.Shell$shell

反射java.lang.Runtime

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class<?> shell=Class.forName("java.lang.Runtime");//获取Runtime类
        Method getRuntime=shell.getMethod("getRuntime");//获取getRuntime方法
        Method exec=shell.getMethod("exec", String.class);//获取exec方法
        exec.invoke(getRuntime.invoke(shell),"calc");//单例模式的反射调用
    }
}
另一种方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassLoader c= ClassLoader.getSystemClassLoader();//创建类加载器
        Class<?> shell=c.loadClass("java.lang.Runtime");//加载类
        Method getRuntime=shell.getMethod("getRuntime");//获取getRuntime方法
        Method exec=shell.getMethod("exec", String.class);//获取exec方法
        exec.invoke(getRuntime.invoke(shell),"calc");//单例模式的反射调用
    }
}