大家可能都听说过Java的类加载机制,是双亲委托模式,听起来有点神秘,但是为什么会叫这个名称呢?
今天我们就来简单的介绍下!
所谓双亲委托,
1. 就是首先当前ClassLoader判断该Class是否已经加载;
2. 如果没有,就委托给父加载器进行查找(自己先不查找),这样依次的进行递归,直到委托到最顶层的ClassLoader;
3. 如果最顶层ClassLoader找到了这个Class,就直接返回,如果没有,就继续依次向下查找;
4. 如果还没找到,就会交由 当前ClassLoader 去查找。
话不多说,实践才是真理,我们先来做个Demo,验证下这条规则。
我们首选来自定义一个ClassLoader,如下:
/**自定义--类加载器*/
public class SelfClassLoader extends ClassLoader {
private String path; //表示加载class的路径
public SelfClassLoader(String path) {
this.path = path;
}
@Override
//查找并加载class
protected Class<?> findClass(String name) {
MyUtils.println("SelfClassLoader --- findClass -- name: " + name);
Class tempClass = null;
byte[] classData = loadClassData(name);
if (classData != null) {
MyUtils.println("SelfClassLoader --- defineClass");
tempClass = defineClass(name, classData, 0, classData.length);
}
return tempClass;
}
//将class文件,加载为byte数据
private byte[] loadClassData(String name)
...
很简单的一个类,一个属性path,表示当前需要加载的数据。
覆盖findClass方法,这个方法就是用来加载并查找class的方法,我们将path下的class文件,加载成byte数据,然后调用父类的definClass方法,就可以转换成需要的Class对象。
下面我们来定义一个最简单的类 TestOne,只有一个方法out,如下:
public class TestOne {
public void out() {
System.out.println(" 调用了TestOne.out方法!");
}
}
把它生成的class文件(IDE自动生成),放到磁盘下某个目录(对应SelfClassLoader 的 path),然后来加载并调用试试:
public static void main(String[] args) {
test1();
}
public static void test1() {
SelfClassLoader selfClassLoader = new SelfClassLoader("e:\\class\\");
try {
Class clazz = selfClassLoader.loadClass("com.testjava.TestOne");
if (clazz != null) {
Object testTwo = clazz.newInstance();
ClassLoader loader = clazz.getClassLoader();
int i = 0;
while (loader != null) {
//循环打印 类加载器的父加载器
MyUtils.println(++i + ": 类加载器: " + loader);
loader = loader.getParent();
}
Method out = clazz.getDeclaredMethod("out");
out.invoke(testTwo);
}
...
我们来看看打印的日志:
SelfClassLoader --- findClass -- name: com.testjava.TestOne
SelfClassLoader --- defineClass
1 类加载器: com.testjava.SelfClassLoader@4b67cf4d
2 类加载器: sun.misc.Launcher$AppClassLoader@14ae5a5
3 类加载器: sun.misc.Launcher$ExtClassLoader@12a3a380
调用了TestOne.out方法!
确实如预计效果,调用到了TestOne.out方法,而且我们还打印了TestOne的类加载器结构,它的父加载器有AppClassLoader,而AppClassLoader的父亲是ExtClassLoader,其实还有一个启动加载器,不过因为它是C编写的,这里无法打印。
(注意:AppClassLoader ExtClassLoader是系统自带的类加载器,我们不用管)
现在我们把TestOne的源文件直接放到当前目录下,看我们定义的加载器,还会不会加载它?
打印日志如下:
1: 类加载器: sun.misc.Launcher$AppClassLoader@14ae5a5
2: 类加载器: sun.misc.Launcher$ExtClassLoader@4b67cf4d
调用了TestOne.out方法!
为什么会这样,因为TestOne的加载已经由AppClassLoader完成,所以不需要我们自定义的ClassLoader再去加载。
我们去看下ClassLoader的源码,找找原因:
这就是ClassLoader最核心的方法 loadClass
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //查找当前加载器的缓存,是否有此类
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //父加载器不为空,则交由它去加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); //所有的父加载器都没有加载,则由最底层的儿子来加载
....
return c
}
从中我们找到了原因,确实和开头所说的一样,先看自己有没有,否则委托给父加载器进行查找(自己先不查找),直至最后,由自己来加载。