时间:2021-05-19
其中,类加载器在加载类的时候是使用了所谓的“父委托”机制。其中,除了根类加载器以外,其他的类加载器都有且只有一个父类加载器。
当生成 一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器将成为该类加载器的父类加载器
下面,自定义类加载器。自定义的类加载器必须继承java.lang.ClassLoader类
import java.io.*;public class MyClassLoader extends ClassLoader { private String name; //类加载器的名字 private String path; //加载类的路径 private final String fileType = ".class"; //class文件的扩展名 public MyClassLoader(String name){ super(); //让系统类加载器成为该类加载器的父 类加载器,该句可省略不写 this.name = name; } public MyClassLoader(ClassLoader parent,String name){ super(parent); //显示指定该类加载器的父 类加载器 this.name = name; } @Override public String toString() { return this.name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } //实现自定义的类加载器必须重写findClass方法,否则ClassLoader类中的findClass()方法是抛出了异常 @Override public 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 is = null; byte[] data = null; ByteArrayOutputStream baos = null; try { this.name = this.name.replace(".","\\"); //com.dream.it---->com\dream\it is = new FileInputStream(new File(path + name + fileType)); int ch; while(-1 != (ch = is.read())){ baos.write(ch); //将数据写入到字节数组输出流对象中去 } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); }finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("d:/myapp/serverlib/"); MyClassLoader loader2 = new MyClassLoader(loader1,"loader2"); //loader1作为loader2的父 类加载器 loader2.setPath("d:/myapp/clientlib"); MyClassLoader loader3 = new MyClassLoader(null,"loader3");//父类加载器为null,表明其父类加载器为根类加载器 loader3.setPath("d:/myapp/otherlib"); test(loader2); test(loader3); } public static void test(ClassLoader cl) throws Exception { Class clazz = cl.loadClass("Sample"); Object object = clazz.newInstance(); }}大致说明一下意思:通过指定的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类(new Dog()),当执行Sample类的构造方法中的new Dog()语句时,JVM需要先加载Dog类,到底用哪个类加载器家在呢?
从上述的打印结果中可以看出,加载Sample类的loader1还加载了Dog类,JVM会用Sample类的定义类加载器去加载Dog类,加载过程中也同样采用了父亲委托机制。
为了验证这一点,可以吧D:\myapp\serverlib目录下Dog.class文件删除,然后在D:\myapp\syslib目录下存放一个Dog.class文件,此时打印结果如下:
Sample:loader1Dog:sun.misc.Launcher$AppClassLoader@1b84c92Sample:loader3Dog: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方法,换成如下代码
Class clazz = loader1.loadClass("Sample");Object obj = clazz.newInstance();Sample sample = (Sample)obj;System.out.println(sample.v1);MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,所以MyClassLoader类看不见Sample类。在MyClassLoader类的main方法中使用Sample类,会导致NoClassFoundError错误。
当两个不同命名空间内的类相互不可见时,可采用Java反射机制来访问对象实例的属性和方法。
将上述代码修改:
Class clazz = loader1.loadClass("Sample");Object obj = clazz.newInstance();Field field = clazz.getField("v1");int v1 = field.getInt(obj);System.out.println(v1);此时,可以获取到对象中的v1属性值。利用反射机制,我们可以跨越这种命名空间的限制。
补充:
命名空间:
运行时包:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
如果要使用自定义类加载器加载class文件,就需要继承java.lang.ClassLoader类。ClassLoader有几个重要的方法:protectedC
类加载器java类加载器就是在运行时在JVM中动态地加载所需的类,java类加载器基于三个机制:委托,可见,单一。把classpath下的那些.class文件加
继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下面给出一个小demo
本文研究的主要是Java语言中的自定义类加载器实例解析的相关内容,具体如下。自己写的类加载器需要注意的是:如果想要对这个实例进行测试的话,首先需要在c盘建立一个
类加载机制java类从被加载到JVM到卸载出JVM,整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、