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

本文承接上一篇,继续讨论ClassLoader的使用方法。

首先看一下ClassLoader的类图:

可以看到默认的实现还是挺庞大的,这里面有两组方法和class的加载有关:

这两个方法涉及到类加载的顺序,设计如下:

因为ClassLoader本身是一个abstract class,所以我们可以扩展它,撰写自己的ClassLoader;在ClassLoader中,有两个方法用于类的加载。当我们扩展了多个ClassLoader的实现类时,也应该按照上面的设计加载顺序去实现。

理解了ClassLoader的工作机制,我们可以自己写一个ClassLoader

自己动手写一个ClassLoader

因为loadClass()方法包含有「逐级向上」的机制,因此我们自己写ClassLoader时,为保证灵活性,可以在自己的ClassLoader中实现findClass()方法。这样,当loadClass()找不到所需的Class时,我们的findClass()方法就会起作用。

我们要实现的ClassLoader叫做SimpleClassLoader

public class SimpleClassLoader extends ClassLoader {
...
}

它的构造函数如下:

public SimpleClassLoader(String path) {...}

这个ClassLoader会在「path及其父母录中」逐级向上查找待加载的类。比如:

SimpleClassLoader cl = new SimpleClassLoader("/usr/share");  
cl.loadClass("MyClass");

上面的代码会让SimpleClassLoader/usr/share中查找是否有MyClass.class,如果没有,它会退出一级目录,在/usr中查找,如此类推,知道找到所需要的class,或者没有找到抛出异常。

为实现这样的查找方式,我们的SimpleClassLoader要有一个dirs[]数组,用于保存待搜索的目录:

String[] dirs;

然后我们要可以处理用户给进来的目录,将其扩展成所有需要搜索的目录,设计方法如下:

public void extendClasspath(String path) { ... }

上面的extendClasspath()方法工作原理如下:假设输入的path为/usr/share,那么应该将其扩展成:

/usr/share  
/usr  
/  

扩展完成的目录的字串会被保存进dirs[]数组。

说完了设计思路,以下是完整的代码实现:

package io.alchemystudio.classloader;

import java.util.function.Predicate;
import java.util.stream.Stream;

public class SimpleClassLoader1 extends ClassLoader {
    String[] dirs;

    public SimpleClassLoader1(String path) {
        dirs = path.split(System.getProperty("path.separator"));
        String[] _dirs = dirs.clone();
        for (String dir : _dirs) {
            extendClasspath(dir);
        }
    }

    public String[] getDirs() {
        return dirs;
    }

    public void extendClasspath(String path) {
        String[] segments = path.split("/");
        String[] exDirs = new String[segments.length];
        for (int i = 0; i < (segments.length); i++) {
            exDirs[i] = popd(segments, i);
        }

        String[] newDirs = new String[dirs.length + exDirs.length];
        System.arraycopy(dirs, 0, newDirs, 0, dirs.length);
        System.arraycopy(exDirs, 0, newDirs, dirs.length, exDirs.length);
        dirs = Stream.of(newDirs)
                .filter(Predicate.not(String::isEmpty))
                .toArray(String[]::new);
    }

    private String popd(String[] pathSegments, int level) {
        StringBuffer path = new StringBuffer();
        for (int i = 0; i < level; i++) {
            path.append(pathSegments[i]).append("/");
        }
        return path.toString();
    }

    public static void main(String[] args) {
        SimpleClassLoader1 cl = new SimpleClassLoader1("/Users/weli/projs/java-snippets/target/classes");
        for (String dir : cl.getDirs()) {
            System.out.println(dir);
        }
    }
}

如上所示,我们把class的实现叫做SimpleClassLoader1,因为它还是一个功能不完整的实现,后续我们会继续不断完善补充这个class直到功能完整。

我们可以测试一下上面class的功能是否正确。执行上面的代码的main()方法,输出如下:

/Users/weli/projs/java-snippets/target/classes
/
/Users/
/Users/weli/
/Users/weli/projs/
/Users/weli/projs/java-snippets/
/Users/weli/projs/java-snippets/target/

如上所示,我们生成了包含有各级目录的字串数组。有了dirs,接下来的工作是在这些目录中查找所需要加载的类,我们要实现ClassLoaderfindClass()方法。这个话题放在下一篇里讲。

这篇文章里面讲解的代码在这里:

有兴趣可以下载运行。

这一篇就先讲到这里。

Powered by Jekyll and Theme by solid