浅谈Java自定义类加载器及JVM自带的类加载器之间的交互关系
JVM自带的类加载器:
其关系如下:
其中,类加载器在加载类的时候是使用了所谓的“父委托”机制。其中,除了根类加载器以外,其他的类加载器都有且只有一个父类加载器。
关于父委托机制的说明:
当生成一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器将成为该类加载器的父类加载器
下面,自定义类加载器。自定义的类加载器必须继承java.lang.ClassLoader类
importjava.io.*; publicclassMyClassLoaderextendsClassLoader{ privateStringname;//类加载器的名字 privateStringpath;//加载类的路径 privatefinalStringfileType=".class";//class文件的扩展名 publicMyClassLoader(Stringname){ super();//让系统类加载器成为该类加载器的父类加载器,该句可省略不写 this.name=name; } publicMyClassLoader(ClassLoaderparent,Stringname){ super(parent);//显示指定该类加载器的父类加载器 this.name=name; } @Override publicStringtoString(){ returnthis.name; } publicStringgetPath(){ returnpath; } publicvoidsetPath(Stringpath){ this.path=path; } //实现自定义的类加载器必须重写findClass方法,否则ClassLoader类中的findClass()方法是抛出了异常 @Override publicClassfindClass(Stringname)throwsClassNotFoundException{ byte[]data=this.loadClassData(name); returnthis.defineClass(name,data,0,data.length); } privatebyte[]loadClassData(Stringname){ InputStreamis=null; byte[]data=null; ByteArrayOutputStreambaos=null; try{ this.name=this.name.replace(".","\\");//com.dream.it---->com\dream\it is=newFileInputStream(newFile(path+name+fileType)); intch; while(-1!=(ch=is.read())){ baos.write(ch);//将数据写入到字节数组输出流对象中去 } data=baos.toByteArray(); }catch(Exceptione){ e.printStackTrace(); }finally{ try{ is.close(); baos.close(); }catch(IOExceptione){ e.printStackTrace(); } } returndata; } publicstaticvoidmain(String[]args)throwsException{ MyClassLoaderloader1=newMyClassLoader("loader1"); loader1.setPath("d:/myapp/serverlib/"); MyClassLoaderloader2=newMyClassLoader(loader1,"loader2");//loader1作为loader2的父类加载器 loader2.setPath("d:/myapp/clientlib"); MyClassLoaderloader3=newMyClassLoader(null,"loader3");//父类加载器为null,表明其父类加载器为根类加载器 loader3.setPath("d:/myapp/otherlib"); test(loader2); test(loader3); } publicstaticvoidtest(ClassLoadercl)throwsException{ Classclazz=cl.loadClass("Sample"); Objectobject=clazz.newInstance(); } }
附上findClass()方法的JDK说明
protectedClass>findClass(Stringname)throwsClassNotFoundException Findstheclasswiththespecifiedbinaryname. Thismethodshouldbeoverriddenbyclassloader implementationsthatfollowthedelegationmodel forloadingclasses,andwillbeinvokedbythe loadClassmethodaftercheckingtheparentclass loaderfortherequestedclass.Thedefault implementationthrowsaClassNotFoundException.
大致说明一下意思:通过指定的name来查找类。该方法应该被类加载器的实现类重写,从而能够保证在加载类的时候可以遵循委托机制模型。在loadClass()方法(该方法是由JVM调用的)中,检查其父类加载器之后,该方法再被调用去加载请求的类。默认该方法的实现是抛出了一个ClassNotFoundException异常。
其实,所谓的加载类,无非就是读取.class文件到内存中,所以在findClass()方法中,loadClassData()方法用于读取.class文件的数据,并返回一个字节数组。然后利用ClassLoader类的defineClass()方法将字节数组转换为Class对象。
上述自定义的类加载器loader1,loader2,loader3及JVM自带的类加载器之间的关系如下:
对于各个类加载器,系统的类加载器是从环境变量classpath中读取.class文件实现类的加载;loader1是从目录d:/myapp/serverlib/下读取.class文件;loader2是从目录d:/myapp/clientlib/下读取.class文件,loader3是从目录d:/myapp/otherlib/下读取.class文件
执行结果:
此处我们分析一下出现这种执行结果的原因:
当执行loader2.loadClass(“Sample”)时先由它上层的所有父类加载器尝试加载Sample类。
loader1从D:\myapp\serverliv目录下成功加载了Sample类,所以loader1是Sample类的定义类加载器,loader1和loader2是Sample类的初始类加载器。
当执行loader3.loadClass(“Sample”)时,先由它上层的所有父类加载器尝试加载Sample类。
loader3的父加载器为根类加载器,它无法加载Sample类,接着loader3从D:\myapp\otherlib目录下成功加载Sample类,所以loader3是Sample类的定义类加载器及初始类加载器。
在Sample类中主动使用了Dog类(newDog()),当执行Sample类的构造方法中的newDog()语句时,JVM需要先加载Dog类,到底用哪个类加载器家在呢?
从上述的打印结果中可以看出,加载Sample类的loader1还加载了Dog类,JVM会用Sample类的定义类加载器去加载Dog类,加载过程中也同样采用了父亲委托机制。
为了验证这一点,可以吧D:\myapp\serverlib目录下Dog.class文件删除,然后在D:\myapp\syslib目录下存放一个Dog.class文件,此时打印结果如下:
Sample:loader1 Dog:sun.misc.Launcher$AppClassLoader@1b84c92 Sample:loader3 Dog:loader3
由此可见,当由loader1加载的Sample类首次主动使用Dog类时,Dog类由系统类加载器加载,如果把D:\myapp\serverlib和D:\myapp\syslib目录下的Dog.class文件都删除,然后在D:\myapp\client目录下存放一个Dog.class文件。
此时文件结构如下图所示:
当Loader1加载Sample类首次主动使用Dog类时,由于loader1及其父类加载器都无法加载Dog类,因此test(loader2)会抛出ClassNotFoundExcption.
这又是因为什么原因呢?
这又牵扯到命名空间的问题。
同一个命名空间内的类时相互可见的。
子加载器的命名空间包含所有父类加载器的命名空间,因此由子加载器加载的类能看见父类加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。由父加载器加载的类不能看见子加载器加载的类。
如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见。
对于上述问题,loader1可以加载Sample类,而Dog类只能由loader2加载Dog类,loader1是Loader2的父类加载器,父加载器loader1加载的类Sample不能看见子加载器loader2加载的类Dog,所以会抛出异常。
对于上述实例中的main方法,我们不调用test方法,换成如下代码
Classclazz=loader1.loadClass("Sample"); Objectobj=clazz.newInstance(); Samplesample=(Sample)obj; System.out.println(sample.v1);
MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,所以MyClassLoader类看不见Sample类。在MyClassLoader类的main方法中使用Sample类,会导致NoClassFoundError错误。
当两个不同命名空间内的类相互不可见时,可采用Java反射机制来访问对象实例的属性和方法。
将上述代码修改:
Classclazz=loader1.loadClass("Sample"); Objectobj=clazz.newInstance(); Fieldfield=clazz.getField("v1"); intv1=field.getInt(obj); System.out.println(v1);
此时,可以获取到对象中的v1属性值。利用反射机制,我们可以跨越这种命名空间的限制。
补充:
命名空间:
运行时包:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。