黑马程序员-----------类加载器的基础学习

来源:互联网 发布:博微电力造价软件 编辑:程序博客网 时间:2024/06/08 09:57

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

类加载器的基础学习

1.什么是类加载器。

     顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

 总结:类加载器java中一个加载类的工具。

 

2.类加载器的树状组织结构


示意图:

 

 Java虚拟机中可安装多个类加载器,系统默认的只有三个加载器。每个负责特定位置的类。

 Bootstrap:

 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader加载目录:JRE/Lib/rt.jar里面的类

 ExtclassLoader:

 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。加载目录:JRE/Lib/ext/*jar(扩展java包)

 AppClasLoader:

 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。加载目录:CLASSPATH指定的所有jar或目录下。

 

 类加载器的组织结构是呈树状的继承关系。

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

例:获取加载器的树状结构。

public class ClassLoaderTest {

public static void main(String[] args) {

//打印类加载器的类名

sop(ClassLoaderTest.class.getClassLoader().getClass().getName());

  //结果为NULL不是指它没有类加载器而是加载器为Bootstrap

sop(System.class.getClassLoader());

        //打印类加载器的树形结构

ClassLoader loader =                                         ClassLoaderTest.class.getClassLoader();

while(loader != null){

sop(loader.getClass().getName());

loader = loader.getParent();

}

sop(loader);

}

public static void sop(Object obj){

System.out.println(obj);

}

}

3.加载器中委托机制

在当前线程的类加载器去加载线程的第一个类时。它要先委托它的上级类加载器。以此递归,直到祖宗加载器。(也就是Bootstrap),然后开始从祖宗类寻找,祖宗类寻找不到,开始下一级ExtclassLoader寻找,又寻找不到再接着下一级AppClasLoader寻找,直达回到发起者类加载器,还加载不到则,抛出异常classNotFoundException。这其中不会到发起者子类去寻找。

1、每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。

2、加载类的方式

        Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?

         1)首先,当前线程的类加载器去加载线程中的第一个类。

         2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B

         3)还可直接调用ClassLoaderLoaderClass()方法,来指定某个类加载器去加载某个类。

2、每个类加载器加载类时,又先委托给上级类加载器。

        类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行加载。当回退到最初的发起者类装载器时,如果它自己也不能完成类的装载,那就会抛出ClassNotFoundException异常。这时就不会再委托发起者加载器的子类去加载了,如果它还有子类的话。

        简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再助剂让其子级找,直到发起者,再没找到就报异常。

3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。

补充:面试题

        可不可以自己写个类为:java.lang.System呢?

        回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System

        第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。

体现委托机制的示例:

package cn.itheima.demo;public class ClassLoaderDemo {public static void main(String[] args) {ClassLoader loader=ClassLoaderDemo.class.getClassLoader();while (loader!=null) {System.out.println(loader.getClass().getName());loader=loader.getParent();//将此loader的上级赋给loader}System.out.println(loader);}}

4.判断两个类是否相同。

Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。

 

5.自定义加载器

1.除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可

2、覆写findClass(Stringname)方法的原因:

        1)在loadClass()内部是会先委托给父级,当父级找不到后返回,再调用findClass(String name)方法,也就是你自定义的类加载器去找。所以只需要覆写findClass方法,就能实现用自定义的类加载器加载类的目的。

        因为,一般自定义类加载器,会把需要加载的类放在自己指定的目录中,而java中已有的类加载器是不知道你这个目录的,所以会找不到。这样才会调用你复写的findClass()方法,用你自定义的类加载器去指定的目录加载类。

        2)这是一种模板方法设计模式。这样就保留了loadClass()方法中的流程(这个流程就是先找父级,找不到再调用自定义的类加载器),而我们只需复写findClass方法,实现局部细节就行了。

        ClassLoader提供了一个protected  Class<?>defineClass(String name, byte[] b, int off, int len)方法,只需要将类对应的class文件传入,就可以将其变为字节码。

 

a) java.lang.ClassLoader类常用到的方法

 

b) 编程步骤

编写一个文件内容进行加密程序。

编写一个自己的类装载器,可实现对加密的类进行装载和解密。

编写一个程序调用类加载器加载类,在源程序中不能用该类各定义引用变量,因为编译器无法识别这个类,程序中可以除了使用ClassLoader.loadClass方法外,还可以使用设置线程的上下文类加载或者系统加载器,然后再使用Class.forName.

4、编码步骤:

        1)对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast

        2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast

        3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

        4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

package cn.itheima.demo;

import java.util.Date;//定义一个测试类,继承Date,便于使用时加载public class ClassLoaderAttachment extends Date{//复写toString方法public String toString(){return "Hello World!";}}import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;public class MyClassLoader extends ClassLoader{public static void main(String[] args) throws Exception {String srcPath=args[0];//文件源String destDir=args[1];//文件目的InputStream ips=new FileInputStream(srcPath);String destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1);String destFilePath=destDir+"\\"+destFileName;OutputStream ops=new FileOutputStream(destFilePath);cypher(ips,ops);//加密class字节码ips.close();ops.close();}//加密方法private static void cypher(InputStream ips,OutputStream ops) throws Exception{int b=-1;while((b=ips.read())!=-1){ops.write(b^0xff);}}@Override//覆盖ClassLoader的findClass方法protected Class<?> findClass(String name) throws ClassNotFoundException {name=name.substring(name.lastIndexOf(".")+1);String classFileName=classDir+"\\"+name+".class";//获取class文件名InputStream ips=null;try {ips=new FileInputStream(classFileName);ByteArrayOutputStream bos=new ByteArrayOutputStream();//定义字节数组流cypher(ips,bos);//解密ips.close();byte[] buf=bos.toByteArray();//取出字节数组流中的数据return defineClass(null, buf,0,buf.length);//加载进内存} catch (Exception e) {// TODO: handle exceptione.printStackTrace();}return null;//return super.findClass(name);}private String classDir;public MyClassLoader(){}//带参数的构造函数public MyClassLoader(String classDir){this.classDir=classDir;}}import java.util.Date;public class ClassLoaderDemo {public static void main(String[] args) throws Exception {//将用自定义的类加载器加载.class文件Class clazz=new MyClassLoader("itheimalib").loadClass("cn.itheima.demo.ClassLoaderAttachment");Date d1 =  (Date)clazz.newInstance();//获取Class类的实例对象System.out.println(d1);}}



0 0