(旧文整理)彻底搞懂Java ClassLoader(一)

(归档:https://www.iteye.com/topic/1126847)

在Java的世界中,ClassLoader用来将Java类装载进JVM使之可以运行。当我们使用java命令使用一些.class的java class时,系统会使用默认的ClassLoader将class读入。

在本文中,将详细探讨ClassLoader的使用。

动态加载类的几种方法

可以使用下面的几种方法将Class读入:

方法 1 使用 .class

下面的代码会让String[]这个class被JVM读入:

Class c = String[].class;  

此时这个class就已经被加载并得到了对应的Class

方法 2: 使用实例的getClass()方法

如果是实例(instance),那么调用它的getClass()方法就可以得到对应的Class。下面是代码示例:

c = new String[1].getClass();  

最后还有通过ClassforName()方法来加载class。

方法 3: 使用 Class.forName()

c = Class.forName("[Ljava.lang.String;");  

上面使用了Class.forName()方法来直接加载字串里面表示的方法。

其中第三个方法中的类名有点奇怪,为什么java.lang.String前面有个L?这个其实是Java的bytecode的命名约定。

在Java中,我们可以用Class.forName()来获取类,比如下面这个例子:

System.out.println(Class.forName("Demo1"));  

运行结果如下:

class Demo1  

但是我们用这种方法获取数组时却会出错:

System.out.println(Class.forName("java.lang.String[]"));  

运行结果如下:

Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[]  
     at java.lang.Class.forName0(Native Method)  
     at java.lang.Class.forName(Class.java:169)  
     ...  

这是为什么呢?因为Array的名字与我们看到的并不一致,可以用下述代码测试:

System.out.println(String[].class.getName());  

得到的结果如下:

[Ljava.lang.String;

可以看到,JVM对Array的实际类名编码为:

因此我们用上面的规则来获取Array class

Class cls = Class.forName("[Ljava.lang.String;");

因此我们可以得到native type的Array的编码:

System.out.println("byte[]: " + byte[].class.getName());  
System.out.println("char[]: " + char[].class.getName());  
System.out.println("int[]: " + int[].class.getName());  
System.out.println("long[]: " + long[].class.getName());  
System.out.println("double[]: " + double[].class.getName());  
System.out.println("float[]: " + float[].class.getName());  

结果如下:

byte[]: [B  
char[]: [C  
int[]: [I  
long[]: [J  
double[]: [D  
float[]: [F

可以看到每一种native类型数组对应的JVM中的类名,我们需要知道Java语言的这个约定习惯。

什么是ClassLoader

ClassLoader是Java中的类加载器,可以帮助你把一个Class动态地加载进JVM。每一个JVM环境中,都有一个默认的ClassLoader,我们可以用下述方法获得默认的ClassLoader:

ClassLoader.getSystemClassLoader()

我们撰写一个ClassLoaderName如下所示:

public class ClassLoaderName {
    public static void main(String[] args) throws Exception {
        System.out.println(ClassLoader.getSystemClassLoader().toString());
    }
}

上面的代码运行结果如下:

jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17

从上面的结果可以看到实际的class loader。

ClassLoader的使用方法

下面给出一个完整的使用class loader加载class的例子,代码如下:

package io.alchemystudio.classloader;

public class MyClass {
    public static void main(String[] args) throws Exception {
        Class clazz = ClassLoader
                .getSystemClassLoader()
                .loadClass("io.alchemystudio.classloader.MyClass");
        MyClass myClass = (MyClass) clazz.getDeclaredConstructor().newInstance();
        myClass.sayHello();
    }

    private void sayHello() {
        System.out.println("Hello, world!");
    }
}

如上所示,我们首先使用ClassLoader.getSystemClassLoader().loadClass()方法来加载MyClass;然后通过clazz.getDeclaredConstructor().newInstance()生成class的实例,最后使用这个实例的sayHello()方法。执行上面的代码结果如下:

Hello, world!

以上就是class loader的一个基础的使用方法。在本系列文章的后续文章中,我们将动手制作一个ClassLoader,并学习ClassLoader的命名空间Namespace。

本系列文章中使用的源代码

接下来的讲解过程中,要大量用到代码示例,我将这些代码统一放到了github上面:

这是一个基于maven的项目,因此可以很方便地打包执行,接下来的讲解将大量用到这个项目中的代码。

这篇文章的样例代码放在了这里:

有兴趣可以运行看看。

Powered by Jekyll and Theme by solid