类加载器是Java中重要的知识,关于类加载器的介绍网上有很多文章,大家可以去找找,我也写了一篇关于类加载器的博客,大家也可以去看看http://blog.csdn.net/TimHeath/article/details/52892531
在讲父委托机制之前先把类加载器体系图贴上来
注意,自定义类加载器的父加载器未必是系统类加载器。可以看见,系统类加载器的父加载器是扩展类加载器,扩展类加载器的父加载器是根类加载器,而根类加载器没有父加载器。其实,除了根类加载器,所有的加载器都有父加载器。
类加载器之间的父子关系并不是Java类中的继承关系,大家要明白,同一个类的不同类加载器之间也可以是父子关系,如上图中的自定义类加载器CustomClassLoader1和CustomClassLoader2,它们可以属于同一个类,然后指定它们的父加载器也可以是同一个类的对象,如,指定CustomClassLoader1的父加载器为CustomClassLoader2也是可以的。在没有指定的情况下系统类加载器便会成为它的父类加载器。
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层级的类加载都是如此,因此所有的加载请求最终都应该传送到顶层的根类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。当所有的父类加载器和父类加载器以上的加载器都没加载成功的时候才会轮到当前的类加载器加载,如果当前的类加载器还没加载成功,则会抛出异常,下面的测试会验证这个事情。
为什么要这样子设计了,我也从网上找了一下,基本上是为了安全着想,如下面所说
父亲委托机制的优点是能够提高软件系统的安全性。因为在此机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。例如,java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。
好了,接下来就验证一下是不是他所说的那样。
public class Dog {
public Dog() {
System.out.println("Dog is loaded by : " + Dog.class.getClassLoader());
}
}
这是我准备的自定义加载类,关于自定义类加载器更详细的描述大家可以去看看我写的另一篇博客http://blog.csdn.net/timheath/article/details/52884482,也可以网上找一下,这里便不在详细描述了。
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 自定义类加载器
*
* @author Jason
*
*/
public class CustomClassLoader extends ClassLoader {
private String name;// 类加载器的名字,方便看测试结果
private String basPath;// 指定加载类的基本路径
private final String fileType = ".class";// 加载文件的扩展名
public CustomClassLoader(String name) {
super();// 让系统类加载器成为该类加载器的父加载器
this.name = name;
}
public CustomClassLoader(String name, ClassLoader parent) {
super(parent);// 显示指定类加载器的父加载器
this.name = name;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name) {
InputStream in = null;
byte[] data = null;
ByteArrayOutputStream bos = null;
try {
// 因为参数传过来的值是edu.jyu.Dog这种类型的,需要替换成edu\jyu\Dog这种类型才能读取
name = name.replace(".", "\\");
in = new FileInputStream(new File(basPath + name + fileType));
bos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = in.read())) {
bos.write(ch);
}
data = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}
@Override
public String toString() {
return this.name;
}
public String getBasPath() {
return basPath;
}
public void setBasPath(String basPath) {
this.basPath = basPath;
}
}
下面是测试代码
/**
* 父委托机制测试
*
* @author Jason
*
*/
public class Test {
public static void main(String[] args) throws Exception {
// 创建一个自定义类加载对象,并设定该类加载的父加载器为系统类加载
CustomClassLoader loader1 = new CustomClassLoader("loader1");
loader1.setBasPath("d:\\myapp\\loader1lib\\");
// 创建一个自定义类加载对象,并设定该类加载的父加载器为loader1
CustomClassLoader loader2 = new CustomClassLoader("loader2", loader1);
loader2.setBasPath("d:\\myapp\\loader2lib\\");
// 创建一个自定义类加载对象,并设定该类加载的父加载器为根类加载器
CustomClassLoader loader3 = new CustomClassLoader("loader3", null);
loader3.setBasPath("d:\\myapp\\loader3lib\\");
test(loader2);// 指定类加载器loader2去加载Dog类
test(loader3);// 指定类加载器loader3去加载Dog类
}
public static void test(ClassLoader loader) throws Exception {
Class clazz = loader.loadClass("Dog");
Object object = clazz.newInstance();
}
}
Dog is loaded by : loader1
Dog is loaded by : loader3
在上面的代码中,我将loader1的父加载器设为系统类加载器,loader2的父加载器为loader1,loader3的父加载器为根类加载器,那么在执行test(loader2);这句代码的时候,是指定loader2加载器去加载Dog类,而loader2便会委托给它的父加载器loader1去加载,而loader1又往上委托给系统类加载器,系统类加载器委托给扩展类加载器,扩展类加载器委托给根类加载器,很明显loader1上面的几个类加载器都找不到Dog类的,所有又交回给loader1加载,刚好loader1加载路径D:\myapp\loader1lib下有一个Dog.class文件,所有就加载了进来,然后再返回引用给loader2。所以虽然我指定的是loader2去加载Dog类,但是实际上Dog类还是被loader1给加载了。
同理可得,执行test(loader3);这句代码,我指定loader3加载Dog类的时候,会先委托loader3的父加载器也就是根类加载器加载,根类加载器找不到所有才返回让loader3加载,而loader3的加载路径D:\myapp\loader3lib下刚好有Dog.class文件,所有就加载了。
如果上面的内容有错误的地方或者讲的不好的地方,还请大家指点一下,我好及时修改。