每个类加载器都有自己的命名空间。
和我们Java中的Package的概念是一样的,和XML中的namespace的概念类似。
同一个命名空间内的类是相互可见的,命名空间由该加载器及所有父加载器所加载的类组成。
比如说loader1上面有父加载器,父加载器和所加载的所有的类在一个命名空间里面。子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器的类。例如系统类加载器加载的类能看见根类加载器加载的类。由父亲加载器加载的类不能看见子加载器加载的类。如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载类相互不可见。
在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
比如有两个加载器A和B加载同一个类,他们如果不是父子关系的话,A已经把类加载到内存当中,B也可以把类加载到内存当中。但如果A和B是父子关系,那么他们只有先加载的才能将类加载到内存。因为加载的条件首先判断是否已经加载了这个类,如果没有加载则进行加载,如果加载就不会再进行加载。
把博客《JVM(十一) 创建用户自定义的类加载器》的main方法稍做一些变化。
public static void main(String[] aregs) throws Exception
{
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("d:\\myapp\\serverlib\\");
MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");
loader2.setPath("d:\\myapp\\clientlib\\");
/*MyClassLoader loader3 = new MyClassLoader(null,"loader3");
loader3.setPath("d:\\myapp\\otherlib\\");
test(loader2);
test(loader3);*/
Class clazz = loader1.loadClass("Sample");
Object object = clazz.newInstance();
Sample sample = (Sample)object;
System.out.println(sample.v1);
}
输出结果(命令:java MyClassLoader):
Sample is loaded by: loader1
Dog is loaded by :loader1
Exception in thread "main" java.lang.NoClassDefFoundError: Sample
at MyClassLoader.main(MyClassLoader.java:88)
Caused by: java.lang.ClassNotFoundException: Sample
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
分析:
MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载,因此MyClassLoader类看不见Sample类。在MyClassLoader类的main()方法中使用Sample类,会导致NoClassDefFoundError错误。
当两个不同命名空间内的类相互不可见时,可采用Java反射机制来访问对方实例的属性和方法。如果把MyClassLoader类的main()方法替换为如下代码:
public static void main(String[] aregs) throws Exception
{
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("d:\\myapp\\serverlib\\");
MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");
loader2.setPath("d:\\myapp\\clientlib\\");
/*MyClassLoader loader3 = new MyClassLoader(null,"loader3");
loader3.setPath("d:\\myapp\\otherlib\\");
test(loader2);
test(loader3);*/
/*Class clazz = loader1.loadClass("Sample");
Object object = clazz.newInstance();
Sample sample = (Sample)object;
System.out.println(sample.v1);*/
Class clazz = loader1.loadClass("Sample");
Object object = clazz.newInstance(); //创建一个Sample类的对象
Field field = clazz.getField("v1");
int v1 = field.getInt(object);
System.out.println("v1:"+v1);
}
输出结果:
Sample is loaded by: loader1
Dog is loaded by :loader1
v1:1
如果把D:\myapp\serverlib目录下的Sample.class和Dog.class删除,再把这两个文件拷贝到D:\myapp\syslib目录下,然后运行main()方法,也能正常运行。此时MyClassLoader类和Sample类都由系统类加载器加载,由于它们位于同一个命名空间内,因此相互可见。