(旧文整理)彻底搞懂Java ClassLoader(二)
本文承接上一篇,继续讨论ClassLoader
的使用方法。
首先看一下ClassLoader
的类图:
可以看到默认的实现还是挺庞大的,这里面有两组方法和class的加载有关:
findClass()
loadClass()
这两个方法涉及到类加载的顺序,设计如下:
- 首先调用的
loadClass()
进行类的加载。 - 如果
loadClass()
找不到要加载的类,就调用自己的「父类」的loadClass()
方法试着加载。 loadClass()
逐级向上,如果一直都找不到,调用「自己」的findClass()
方法进行类的加载。- 如果所有方法尝试后均找不到要加载的类,则抛出
ClassNotFoundException
。
因为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
,接下来的工作是在这些目录中查找所需要加载的类,我们要实现ClassLoader
的findClass()
方法。这个话题放在下一篇里讲。
这篇文章里面讲解的代码在这里:
有兴趣可以下载运行。
这一篇就先讲到这里。