先把知识点总结一下:
1.每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
2.在同一个命名空间里,不会出现类的完整名字(包括类的包名)相同的两个类。
3.在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
4.子加载器加载的类能加载父加载器加载的类。
5.父加载器加载的类不能加载子加载器锁加载的类。
后面会详细的解释上面所述,请继续关注后面的博客。
先举个例子,里面后面包含四个类:
JavaTest1.java:用来调用加载器加载的主类。
JVMTest1.java:自定义类加载器。
People.java:类中只包含一个无参构造函数,在其中创建了一个Man类的对象。
Man.java:只包含一个无参构造函数。
接下来依次贴代码:
JavaTest1.java
public class JavaTest1 {
public static void main(String[] args) throws Exception {
/*loader1加载器*/
JVMTest1 loader1 = new JVMTest1("loader1");
loader1.setPath("C:\\Users\\admin\\Desktop\\");
Class> clazz1 = loader1.loadClass("People");
System.out.println("clazz1的加载器:"+clazz1.getClassLoader());
Object object = clazz1.newInstance();//实验二
}
}
JVMTest1.java(不理解代码的可以查阅一下我上篇博文)。
import java.io.*;
public class JVMTest1 extends ClassLoader{
private String classLoaderName;
private final String fileExtension = ".class";
private String path;
public void setPath(String path) {
this.path = path;
}
public JVMTest1(String classLoaderName){
super();
this.classLoaderName = classLoaderName;
}
public JVMTest1(ClassLoader parent, String classLoaderName){
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class> findClass(String className){
byte[] data = this.loadClassData(className);
System.out.println("运行一次自定义的类加载器"+this.classLoaderName);
return this.defineClass(className,data,0, data.length);
}
private byte[] loadClassData(String className) {
FileInputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
className = className.replace(".","\\");
is = new FileInputStream(new File(this.path+className+ this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while((ch = is.read())!=-1){
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex){
ex.printStackTrace();
}finally {
try{
is.close();
baos.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
}
People.java
public class People {
public People(){
System.out.println("People的ClassLoader是:"+ this.getClass().getClassLoader());
new Man();
//下面这句话是实验六的注释
//System.out.println("在People中打印Man.class:"+Man.class);
}
}
Man.java
public class Man {
public Man(){
System.out.println("Man的ClassLoader是:"+ this.getClass().getClassLoader());
//下面这句话是实验七的注释
// System.out.println("在Man中打印People.class:"+People.class);
}
}
1.正常运行上述代码,观测运行结果。
2.因为这People类能被系统类加载器加载,Man类在People类中被创建,属于对Man类的主动使用,所以它需要被加载然后被系统类加载了,这没有太大毛病吧?
3.将JavaTest1中注释“实验二”那一行注释掉,观测运行结果。
4.为什么写在People.java和Man.java的输出没有运行的?大家看一下,咱们的输出语句都写在哪了?写在构造函数中了,咱们在JavaTest中是只让它加载,没让它运行,所以需要创建一个People类的实例,它才会运行。
5.将这两个类移到桌面(这样做的目的是让系统类加载器无法加载,这样咱们的自定义的加载器才发挥作用),观测运行结果。
6.People类和Man类被咱们写的自定义加载器顺利加载。
7.将People.class移至桌面,观察结果
8.在加载People.class时,系统类加载器找不到在哪,所以只能由自定义的加载器查找加载。
在加载Man.class时,系统类加载器能找到,所以Man被AppCLassLoader加载。
9.这次同样将Man.class移到桌面,观察运行结果。
10.看People类顺利被系统类加载,而Man类却找不到,有些同学可能会疑惑,Man.class系统类加载器加载不了,自定义的类加载器应该能加载啊!大家在仔细看看咱们自定义加载的代码
这是指明People.class在桌面的存放地址,没有Man的,所以程序会报错。
11.将People.java移至桌面,把People.java中的注释去掉,观测运行结果。
12.大家看People是自定义类加载器加载的,Man是系统类加载的,而在People中能使用系统类加载的类。
13.将People.java移至桌面,把Man.java中的注释去掉,观测运行结果。
14.直接报错,这是因为在子加载器加载的类能加载父加载器加载的类,父加载器加载的类不能加载子加载器锁加载的类。他们存在一种包含关系,关系如下:
不理解之处,欢迎留言讨论。