黑马程序员-java学习笔记-类加载器

来源:互联网 发布:淘宝双11营业额2017 编辑:程序博客网 时间:2024/05/21 09:46

---------------------- android培训、java培训、期待与您交流! ----------------------

类加载器,顾名思义就是用来加载类的工具了。

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader

类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,既然这样第一个类加载器又是谁加载的呢?

显然必须有第一个类加载器不是不是java类,这正是BootStrap。

Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

我们试着去打印一下类加载器的名称

public class ClassLoaderTest {public static void main(String[] args) throws Exception {// TODO Auto-generated method stubSystem.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());}
结果打印


我们再来打印一下System类到底是由哪个类加载器加载的

System.out.println(System.class.getClassLoader().getClass().getName());

结果报错了,空指针异常


原因分析,只要有一个java对象加载进来,则肯定可以getClass(),所以应该就是获取类加载器的时候为空了。
打印一下

System.out.println(System.class.getClassLoader());
结果为null。说明这是由BootStrap加载器加载的。该加载器是不一个java类对象,是嵌套在java虚拟机上面的。

上面所说,Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织。

那我们来打印一下类加载器的树形结构关系

ClassLoader loader = ClassLoaderTest.class.getClassLoader();while(loader != null){System.out.println(loader.getClass().getName());loader = loader.getParent();}System.out.println(loader);
打印结果,最后的null代表BootStrap,是所以类加载器的祖先



用eclipse的打包工具将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包,再在eclipse中运行这个类,运行结果显示为ExtClassLoadr。此时的环境状态是classpath目录有ClassLoaderTest.class,ext/itcast.jar包中也有ClassLoaderTest.class,这时候我们就需要了解类加载的具体过程和原理了。

类加载的之间的父子关系和管辖范围
优先由父类加载,如上面所说的,存在了两份.class文件,则按照父类先加载的原则,由ExtClassLoader进行加载
extClassLoader类加载器专门是加载图上指定目录的.jar文件,只要放文件到该目录,都有该加载器加载。



类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1)首先当前线程的类加载器去加载线程中的第一个类。
2)如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。 
3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。
1)当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
2)每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

问:能不能自己写个类叫java.lang.System?
答:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,
这样总是使用java系统提供的System。

把先前编写的类加入到jdk的rt.jar中,会有怎样的效果呢?不行!!!
看来是不能随意将自己的class文件加入进rt.jar文件中的。

编写自己的类加载器,加载特定目录中的类,其他加载器不可以加载

这里涉及三个知识点
1)自定义的类加载器的必须继承ClassLoader,这是一个抽象类,所以不能直接用,只能用它的子类
2)loadClass方法与findClass方法
loadClass()方法是在Classloader类中的,那是不是需要覆盖这个方法呢?不需要的,因为在该方法调用后,会按照类加载器的委托机制先去寻找父类加载器,接着再去调用findClass()方法,所以呢,为了保留类加载的委托机制,我们不用去覆盖loadClass()方法,而是去覆盖findClass()方法即可,在findClass()中调用自己的类加载器。
3)defineClass方法
当我们得到class文件之后,怎样才能转换成字节码(class对象)呢?这就用到了defindClass()方法,只要把class文件传入该方法,就好返回该class的字节码(class对象)
我们增加一下难度,将.class文件进行加密,先编写一个加密方法
public class MyClassLoader extends ClassLoader{/** * @param args */public static void main(String[] args) throws Exception {// TODO Auto-generated method stubString srcPath = args[0];//获取.class文件所在目录String destDir = args[1];//加密后的文件输入目录FileInputStream fis = new FileInputStream(srcPath);String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);String destPath = destDir + "\\" + destFileName;FileOutputStream fos = new FileOutputStream(destPath);cypher(fis,fos);//开始加密fis.close();fos.close();}//加密解密方法private static void cypher(InputStream ips ,OutputStream ops) throws Exception{int b = -1;while((b=ips.read())!=-1){ops.write(b ^ 0xff);//异或的方法进行加密,这样1变0,0变1}}


接着编写一个类,该类继承了Date()类,并且覆盖了toString()方法,这个类对应的的.class文件就我们等下我们要加密的.class文件了。
public class ClassLoaderAttachment extends Date {public String toString(){return "hello,itcast";} }

然后我们再eclipse的工程中建立一个目录itcastlib,用于保存加密后的.class文件,这个目录只能用我们自定义的类加载加载里面的class文件

我们找到ClassLoaderAttachment类的.class文件,然后去配置main()方法的参数,将.class所在目录(这里是完整名称)和加密后的文件目录传入

加密成功后,itcastlib中就存在了加密后的ClassLoaderAttachment.class文件了。

拿着加密后的.class文件去覆盖掉原先的.class文件,结果肯定会报一大堆的错误,因为系统默认的加载器不认识该.class文件

这时我们就需要编写自己的类加载器了。
在刚才我们编写加密方法的类MyClassLoader,我们继续扩展它
定义一个变量
private String classDir;//用于保存目录
接着定义构造方法
public MyClassLoader(){}public MyClassLoader(String classDir){this.classDir = classDir;}
覆盖findeclass()方法
@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {//得到.class文件的觉得路径String classFileName = classDir + "\\"  + name.substring(name.lastIndexOf('.')+1) + ".class";try {FileInputStream fis = new FileInputStream(classFileName);ByteArrayOutputStream  bos = new ByteArrayOutputStream();cypher(fis,bos);//解密.class文件fis.close();System.out.println("aaa");//这个用于测试是否运行了该方法byte[] bytes = bos.toByteArray();return defineClass(bytes, 0, bytes.length);//将.class文件转换为class对象返回} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return null;}
接着编写调用方法
Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");Date d1 =  (Date)clazz.newInstance();System.out.println(d1);
运行之前,先删除掉原来的ClassLoaderAttachment.class文件,这样安装流程走会运行MyClassLoader类的loadClass()方法,其父类的加载器会先运行,但是找不到原先的ClassLoaderAttachment.class文件,这时就运行findClass()方法,该方法我们已经覆盖了,所以会去找指定目录的.class文件,也就是在itcastlib中的加密的.class文件。

如果没有删除掉原先的.class文件的话,那父类加载器就会加载,而不是用到自定义的加载器了。

还有一个小问题,为什么在定义ClassLoaderAttachment类的时候,要继承Date()类呢?

因为ClassLoaderAttachment编译器根本不认识,这个.class文件被我们加密了,所以只能用Date来声明引用了。





---------------------- android培训、java培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net

原创粉丝点击