0x1. 类加载器: ClassLoader

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言,Java会先通过编译器将源代码转换为Java二进制字节码,一般是保存在.class文件中,之后通过JVM解释器执行这段代码。字节码文件会包含很多Class信息,在JVM解释器运行的过程中,ClassLoader就是用来加载类的,它会将Java字节码中的Class加载到内存中,而每个Class对象内部都有一个ClassLoader属性标识由哪个ClassLoader加载。

常见的ClassLoader

一切的Java类都必须经过JVM加载之后才可以运行,最常见的ClassLoaderBootstrapClassLoaderExtensionClassLoaderAppClassLoaderURLClassLoaderContextClassLoader

BootstrapClassLoader

JVM内置的默认classLoader,负责加载JVM运行时的核心类,位于JAVA_HOME/lib/rt.jar/文件夹中,由C代码实现,Bootstrap ClassLoader类加载器所加载的类的ClassLoader时候都会返回null

ExtClassLoader

扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,库名通常以 javax 开头

AppClassLoader

应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载ClASSPATH环境变量或者java.class.path属性里定义的路径中的jar包和目录,我们自己编写和使用的第三方Jar包通常都是由它来加载

URLClassLoader

ClassLoader抽象类的一种实现,它可以根据URL搜索类或资源,并进行远程加载。

package demo1;

public class JavaClassLoader {
    public static void main(String[] args){
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extensionClassLoader = classLoader.getParent();
        System.out.println("App Classloader: " + classLoader);
        System.out.println("parent Classloader: " + extensionClassLoader);
        System.out.println("The parent of parent Classloader: " + extensionClassLoader.getParent());
    }
}

执行结果:

App Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
parent Classloader: sun.misc.Launcher$ExtClassLoader@5cad8086
The parent of parent Classloader: null

ClassLoader类有如下常见的方法:

  • loadClass:参数为需要加载的全限定类名,该方法会先查看目标类是否已经被加载,查看父级加载器并递归调用loadClass(),如果都没找到则调用findClass()。这种寻找类的方式称为双亲委派机制(delegation model),主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,同时也避免了重复加载。

    双亲委派:
  • findClass: 搜索类的位置,一般会根据名称或位置加载.class字节码文件,获取字节码数组,然后调用defineClass()。
  • findloadedClass: 查找JVM已经加载过的类
  • defineClass: 将字节码转换为JVM的java.lang.Class对象

代码中关于defineClass还是比较值得一看,loadClass的作用是加载Class文件,转换为字节码,当class不是在文件里面,而是从其它来源的时候,比如网络请求,这个时候就轮到defineClass上场了,defineClass负责把byte[]直接转换为Class,也就是说defineClass是对类加载方式的扩展:

However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method {@link #defineClass(String, byte[], int, int) defineClass} converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using {@link Class#newInstance Class.newInstance}.

Java反射

获取Class对象的四种方法

反射的主要作用是通过Class对象来对类的属性和方法进行获取和调用,包括类的私有方法(protected和private),一般来说有两种方法可以获取类:

  1. obj.getClass(): 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过obj.getClass()来获取它的类
    java.lang.Runtime obj = java.lang.Runtime.getRuntime();
    Class c = obj.getClass();
  2. Class.forName: Class c= Class.forName("java.lang.Runtime");
  3. ClassLoader: Class clazz = Classloader.getSystemClassLoader().loadClass("java.lang.Runtime");
  4. 原生类.class: Class clazz = java.lang.Runtime.class;

forName有两个函数重载:

  • Class<?> forName(String name)
  • Class<?> forName(String name, **boolean** initialize, ClassLoader loader)
    第⼀个就是我们最常⻅的获取class的⽅式,其实可以理解为第⼆种⽅式的⼀个封装:
    Class.forName(className)
    // 等于
    Class.forName(className, true, currentLoader)

    默认情况下,forName的第⼀个参数是类名;第⼆个参数表示是否初始化;第三个参数就是ClassLoader

反射调用函数

package student;

public class Student {
    int age;
    String name;

    public Student() {
        this.age = 18;
        this.name = "D";
    }

    public void sayHello(){
        System.out.println("My name is " + name + ", My Age is " + age);
    }
}

class Test1{
    public static void main(String[] args) throws Exception{
        Student student = new Student();
        student.sayHello();
        System.out.println(student.getClass().getClassLoader());

        //反射调用Student

        String class_name = "student.Student";
        String func = "sayHello";
        Class stu_class = Class.forName(class_name);
        Student stu2 = (Student) stu_class.newInstance();
        Class.forName(class_name).getMethod(func).invoke(stu2);

    }
}
  • getDeclaredMethod(): Returns a Method object that reflects the specified declared method of the class or interface represented by this Class object.
  • getMethod(): returns a Method object that reflects the specified public member method of the class or interface represented by this Class object.

getDeclaredMethod()可以获取类里面任何方法,getMethod()只可以获取public属性的方法。另外几个比较用的多是是:

  • getField(String name): 根据字段名获取对应的字段,只能获取public类型的字段,可以获取父类的字段。
  • getFields(): 获取类所有的字段,只能获取public类型的字段,可以获取父类的字段。
  • getDeclaredField(String name): 根据字段名获取对应的字段,可以获取public、protected和private类型的字段,不能获取父类的字段。
  • getDeclaredFields(): 获取类所有的字段,包括public、protected和private。不能获取父类的字段。

通过反射获取修改私有变量

如果字段是staic修饰的时候,在获取和修改字段的时候,可以使用null代替具体对象的stu

通过反射调用私有方法

如果调用的方法有多个参数,需要以数组的形式传入:

  • 使用getDeclaredMethod获取多个参数的方法,第二个参数为new Class[]{}类型的数组,数组中每一个值对应参数的class对象。这是一种标准的传参方式,建议即使方法没有参数或者只有一个参数也按照这种方式传参
  • 使用method.invoke方法对方法进行调用,传递的第二个参数表示实际调用时传递的参数值,类型是Object数组。
    对于static类型的方法,与字段的使用方法相似,在执行方法时,同样可以把obj对象换成null

反射获取构造函数

构造函数是一种特殊的方法,很多情况下需要通过反射获取构造函数,然后通过构造函数生成类的实例。

  • getConstructor(Class... parameterTypes) 根据参数类型获取对应的构造函数,只能获取public类型的构造函数,不能获取父类的构造函数。
  • getConstructors() 获取类所有的构造函数,只能获取public类型的字段,不能获取父类的构造函数。
  • getDeclaredConstructor (Class... parameterTypes) 根据参数类型获取对应的构造函数,可以获取public、protected和private类型的构造函数,不能获取父类的构造函数。
  • getDeclaredConstructors() 获取类所有的构造函数,包括public、protected和private。不能获取父类的构造函数。

在获取到构造函数之后,需要通过newInstance函数来生成类对象。关于newInstance的使用如下所示:

  • newInstance(Object ... initargs): newInstance函数接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。newInstance返回的数据类型是Object,一般需要强制转换类型。

参考链接

⬆︎TOP