黑马程序员-java-类加载器

时间:2023-02-17 17:19:29
---------------------- android培训java培训、期待与您交流! ----------------------
  我们程序写完会编译生成一个.class文件,java虚拟机运行我们的程序
就是调用类加载器,把硬盘上类的.class字节码(二进制)文件读到内存,放在内存的方法区,
然后在堆内存生成该类的一个Class对象,然后就生成各个类的实例运行了。

类加载器是一个类,所以它也要被别的加载器加载,显然要有第一个类加载器不是java类,
用于去加载别的类,它就是根类加载器BootStrap,
是使用C++编写的,程序员无法在Java代码中获得该类。

java虚拟机总共三个类加载器:
1 根类加载器BootStrap,使用C++编写,程序员无法在Java代码中获得该类,
它的加载目录为:JRE/lib/rt.jar

2 扩展加载器ExtClassLoader  JRE/lib/ext/*.jar

3 系统加载器AppClassLoader  加载classpath路径指向的.class文件

可见,我们平常的类都是AppClassLoader加载的,它加载classpath路径下的类。

类加载器是父亲委托机制的,最上层是BootStrap,然后是ExtClassLoader,
最后是系统加载器AppClassLoader,然后是我们自定义的类加载器,
并且每个加载器只有一个父亲,当加载一个类时,从父亲开始找那个.class文件,
父亲找到,父亲加载,找不到,然后一个个子类找,谁找到谁加载。

看下面例子:
   System.out.println(Test.class.getClassLoader().getClass().getName());
  System.out.println(System.class.getClassLoader());
程序打印:
sun.misc.Launcher$AppClassLoader
null
可见我们平常的类加载器是AppClassLoader
System类找不到加载器,那它肯定是BootStrap加载的

类加载器加载类就是把硬盘上类的.class字节码(二进制)文件读到内存,放在运行时的方法区,然后在堆内存
生成该类的一个Class对象。
我们要创建自己的类加载器,继承java.lang.ClassLoader类,然后
覆盖它的findClass(String name)方法,返回对应的Class对象的引用,
所以我们的findClass方法里肯定就是根据路径找到要加载的那个类,然后
生成一个Class对象。

下面来写我们自己的类加载器:MyClassLoader
package com.virtual;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;

public class MyClassLoader extends ClassLoader
{
  private String name; // 类加载器的名字
 private String path = "d:\\"; // 加载类的路径
 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;
 }
 public String toString()
 {
  return this.name;
 }
 public String getPath()
 {
  return path;
 }
 public void setPath(String path)
 {
  this.path = path;
 }
 public Class<?> findClass(String name)
 {
  //将要加载的.class文件读到内存字节数组里
  byte[] data = this.loadClassData(name);
  //返回对应字节码的Class对象
  return this.defineClass(name, data, 0, data.length);
 }
       private byte[] loadClassData(String name)
 {
  InputStream is = null;
  byte[] data = null;
  ByteArrayOutputStream baos = null;
       try
  {
   //要找的.class文件如果带包名,将点转为"\\"
   this.name = this.name.replace(".", "\\");
   is = new FileInputStream(new File(path + name + fileType));
   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;
 }
  public static void main(String[] args) throws Exception
 {
  MyClassLoader loader1 = new MyClassLoader("loader1");
  loader1.setPath("d:\\myapp\\");
    MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
  loader2.setPath("d:\\myapp\\lib\\");
    MyClassLoader loader3 = new MyClassLoader(null, "loader3");
   loader3.setPath("d:\\myapp\\lib2\\");

   test(loader1);
   test(loader2);
   test(loader3);
}
  public static void test(ClassLoader loader)
 {
  Class clazz;
  try
  {
   clazz = loader.loadClass("Sample");
   Object object = clazz.newInstance();
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
 }
}
public class Sample
{
 public int v1 = 1;
 public Sample()
 {
  System.out.println("Sample is loaded by: " + this.getClass().getClassLoader());
  new Dog();
 }
}
public class Dog
{
 public Dog()
 {
   System.out.println("Dog is loaded by : " + this.getClass().getClassLoader());
 }
}

我们把自己编译好的Sample.class文件和Dog.class文件放到
d:/myapp,d:/myapp/lib和d:/myapp/lib2三个目录中

加载器loader1指定AppClassLoader为自己的父加载器,
loader2指定loader1为自己的父加载器,
loader3没有指定父加载器。

上面程序打印:
Sample is loaded by: loader1
Dog is loaded by : loader1
Sample is loaded by: loader1
Dog is loaded by : loader1
Sample is loaded by: loader3
Dog is loaded by : loader3

三个加载器都去找Sample.class文件去加载,
loader1先派AppClassLoader去加载,没有找到Sample.class,
因为AppClassLoader去classpath下找Sample.class,classpath下
只有com.virtual.Sample这个带包名的字节码文件,
loader2加载器的父亲为loader1,所以跟loader1加载的结果一样,
loader3无父加载器,只有自己加载,所以结果为:
Sample is loaded by: loader3
Dog is loaded by : loader3

如果我们把MyClassLoader文件去掉包名,编译后放到
d:/myapp文件中,用命令行编译将打印:
Sample is loaded by: sun.misc.Launcher$AppClassLoader@19821f
Dog is loaded by : sun.misc.Launcher$AppClassLoader@19821f
Sample is loaded by: sun.misc.Launcher$AppClassLoader@19821f
Dog is loaded by : sun.misc.Launcher$AppClassLoader@19821f
Sample is loaded by: loader3
Dog is loaded by : loader3
这时,AppClassLoader加载器从classpath下找Sample.class和Dog.class
文件都能找到,classpath我们一般设置为:
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
点代表当前路径,然后指向dt.jar和tool.jar两个文件

张孝祥老师的视频中,得到Servlet加载器的关系,
org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
把Web项目中的MyServlet的class文件输出到
JRE/lib/ext/目录下由ExtClassLoader加载,可是
MyServlet extends HttpServlet,HttpServlet由WebappClassLoader加载
就产生了错误,此刻HttpServlet要由ExtClassLoader加载才行。
其实是子类加载器的类可以看见父类加载器加载的类,父类加载器加载的类看不见子类加载器加载的类,
如果两个加载器之间没有父子关系,则它们加载的类互相看不见。
ExtClassLoader为父类,它加载MyServlet,WebappClassLoader为子,它加载HttpServlet,
MyServlet看不见HttpServlet是显然的。

上面的程序中main方法里面的都注释掉这样写:
  public static void main(String[] args)
  {
   MyClassLoader loader1 = new MyClassLoader("loader1");
  loader1.setPath("d:\\myapp\\");
   Class clazz = loader1.loadClass("Sample");
   Sample s = (Sample)clazz.newInstance();
   System.out.println(s.v1);
 }
程序打印:
Sample is loaded by: loader1
Dog is loaded by : loader1
Exception in thread "main" java.lang.ClassCastException: Sample cannot be cast to com.virtual.Sample

由于MyClassLoader由AppClassLoader加载,Sample由loader1加载,所以MyClassLoader里看不到Sample,
自然打印不出s.v1,可以通过反射得到Sample对象里面的属性v1:
    MyClassLoader loader1 = new MyClassLoader("loader1");
  loader1.setPath("d:\\myapp\\");
   Class clazz = loader1.loadClass("Sample");
  Object s = clazz.newInstance();
  Field field = clazz.getField("v1");
  System.out.println(field.getInt(s));
程序打印:
Sample is loaded by: loader1
Dog is loaded by : loader1
1  
好了,关于类加载器的学习就总结到这了,谢谢。

---------------------- android培训java培训 、期待与您交流! ----------------------详细请查看: http://edu.csdn.net/heima