类加载器命名空间实战剖析与透彻理解
命名空间
- 每个类加载器都有自己的命名空间,命名空间是由该加载器及所有的父加载器加载的类组成
- 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
- 在不同的命名空间中,有可能出现类的完整名字(包括类的包名)相同的两个类
关于命名空间的重要说明
1、子加载器所加载的类能够访问到父加载器所加载的类
2、父加载器所加载的类无法访问到子类加载器所加载的类
3、如果两个两个加载器没有直接或者间接的父子关系,那么他们各自加载的类互相不可见
下面是验证这两个重要的说明的。
举例分析
首先说明一下我们下面写的代码的作用是干什么。我们要定义两个类Mysample和Mycat这两个类,我们要把这两个类由不同的类加载器来加载他们,比如我们让AppclassLoader(系统类加载器)加载Mysample,让我们自定一个类加载器MyTest16来加载Mycat,然后在Mysample的构造方法里面实例化Mycat,看输出的结果是什么,然后在Mycat的构造方法里面实例化Mysample看输出的结果是什么。
public class MyTest17_1 {
public static void main(String[] args) throws Exception{
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("E:\\data\\classes\\");
Class<?> clazz = loader1.loadClass("com.twodragonlake.jvm.classloader.MySample");
System.out.println("class :"+clazz.hashCode());
//如果注释掉改行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
//因此不会实例化MyCat对象,既没有对MyCat进行主动使用,这里就不会加载MyCat class
Object object = clazz.newInstance();
}
}
public class MyCat {
public MyCat(){
System.out.println("MyCat is loaded by : "+this.getClass().getClassLoader());
System.out.println("from MyCat : "+MySample.class);//加入打印MySample的一行代码
}
}
public class MySample {
public MySample(){
System.out.println("MySample is loaded by : "+this.getClass().getClassLoader());
new MyCat();
System.out.println("form MySample :"+MyCat.class);
}
}
程序的运行结果
class :1735600054
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
因为这两个类还在classpath下面,所以系统类加载器会加载。
现在我们把classes目录复制到E:\data目录下,删除classpath下的Mysample.class和MyCat.class这两个class文件,
程序的运行结果:
findClass invoked com.twodragonlake.jvm.classloader.MySample【加载MySample时MyTest16的打印】
this.classLoaderName : loader1 【MySample是由MyTest16加载,MyTest16的打印】
class :2133927002 【MyTest17_1的打印】
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6 【MySample构造器】
findClass invoked com.twodragonlake.jvm.classloader.MyCat 【MySample的构造器new MyCat的时候要先加载MyCat】
this.classLoaderName : loader1 【MyCat是由MyTest16加载的】
MyCat is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6 【MyCat构造器的打印】
两个类都是有MyTest16这个类加载器加载的,这没什么问题,并且在MySample的构造方法里面初始话MyCat也是没问题的。
现在我们再修改一下,重新build工程,只删除classpath下的MyCat.class 此时MySample在当前工程和【E:\data\classes\】下都有一份,而MyCat只在【E:\data\classes\】有一份.
程序的运行结果:
class :1735600054
Exception in thread "main" java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MyCat
MySample is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
at com.twodragonlake.jvm.classloader.MySample.<init>(MySample.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at com.twodragonlake.jvm.classloader.MyTest17_1.main(MyTest17_1.java:11)
Caused by: java.lang.ClassNotFoundException: com.twodragonlake.jvm.classloader.MyCat
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more
分析:首先Mysample.class会被AppClassLoader加载,当调用 Object object = clazz.newInstance(); 这一行代码的时候,就是要初始化Mysample,执行MySample的构造方法,new MyCat()的时候报java.lang.NoClassDefFoundError: com/twodragonlake/jvm/classloader/MyCat。
再修改一下 我们只删除classpath下的MySample.class 此时MyCat在当前工程和【E:\data\classes\】下都有一份,而MySample只在【E:\data\classes\】有一份.
程序的运行结果:
findClass invoked com.twodragonlake.jvm.classloader.MySample
this.classLoaderName : loader1
class :2133927002
MySample is loaded by : com.twodragonlake.jvm.classloader.MyTest16@677327b6
MyCat is loaded by : sun.misc.Launcher$AppClassLoader@18b4aac2
form MySample :class com.twodragonlake.jvm.classloader.MyCat
分析:
首先MySample的class不在当前工程下,因此会使用自定义加载器MyTest16加载,MySample的构造器里边出现new MyCat,因此会使用子加载器MyTest16的父加载器应用类加载器加载MyCat,之后【new MyCat()】这行代码是在子类加载器MyTest16里边出现,由于子类加载器可以看到父类加载器加载的类,因此不会抛出异常。
自定义类加载器Mytest16
public class MyTest16 extends ClassLoader{
private String className;
//目录
private String path;
private final String fileExtension = ".class";
public MyTest16(String classLoadName){
super(); //将系统类加载器当做该类加载器的父加载器
this.className = classLoadName;
}
public MyTest16(ClassLoader parent, String classLoadName){
super(parent); //显示指定该类加载器的父加载器器
this.className = classLoadName;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
return "[" + this.className + "]";
}
@Override
protected Class<?> findClass(String clasName) throws ClassNotFoundException {
System.out.println("findClass invoked:" + clasName);
System.out.println("class loader name: " + this.className);
byte[] data = this.loadClassData(clasName);
return this.defineClass(clasName,data, 0, data.length);
}
private byte[] loadClassData(String className){
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try{
className = className.replace(".","//");
//System.out.println("className:" +this.className);
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while ( -1 != (ch = is.read())){
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex){
ex.printStackTrace();
}finally {
try {
is.close();
baos.close();
}catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
}