黑马程序员_java类加载器

来源:互联网 发布:sql in和not in的用法 编辑:程序博客网 时间:2024/06/10 07:15

java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个加载器负责加载特定位置的类。
系统默认的三个类加载器之间的父子关系:BootStrap(爷)------ExtClassLoader(父)-----AppClassLoader(子)
注意:类加载器也是java类,但是这些加载器类本身又由谁来加载呢?显然必须有第一个类加载器不是java类,
这个加载器就是BootStrap,它不是一个java类,是直接在虚拟机里面的一个加载器。其他两个加载器都是java类。

BootStrap加载JRE/lib/rt.jar里面的类
ExtClassLoader加载JRE/lib/ext/*.jar里面的类
AppClassLoader加载classpath指定的所有jar或目录里面的类
我们还可以写自己的类加载器去加载指定的特殊的目录

自定义的类加载器必须继承ClassLoader。
ClassLoader类中的loadClass()方法其实就是委托机制的方法,当一个类加载器加载一个类的时候,loadClass()方法内部就会去委托父加载器类加载,如此往上的委托。
所以我们自定义类加载器时一般不需要重写loadClass()方法。当委托的所有祖宗类加载器没有加载到类的时候就调用自己的findClass()方法,
所以我们写自己的类加载器时一般只需重写ClassLoader类中的findClass()方法即可。
在findClass()方法中我们应该先得到class文件的字节数组,
然后调用从父类ClassLoader继承而来defineClass(String name, byte[] b, int off, int len)方法
利用从class文件得到的字节数组产生一份字节码并返回此字节码(Class对象)即可。

当我们要使用指定的类加载器来加载类的时候可以调用类加载器对象的loadClass()方法。如:
Class clazz = new MyClassLoader().loadClass("com.heima.exam.ClassLoaderAttachment");

(扩展:模版方法设计模式:父类有一个loadClass()方法,他的所有子类都是用同样的流程去找爸爸,爸爸不能干再自己干,用findClass()方法。
所以他的每个子类一般只有自己干的那一部分的代码不一样。   父类-----子类1,子类2,子类3。。。)

类加载器的委托机制:
每个类加载器加载类时,都又先委托给其上级类加载器。也就是说首先让BootStrap类加载器加载。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException。

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

(有包名的类不能调用无包名的类!)

自己写类加载器例子程序如下:
//用来被测试加载的类
package com.heima.exam;

import java.util.Date;

public class ClassLoaderAttachment extends Date {

 @Override
 public String toString() {
  return "hello time!";
 }
}

//类加载器
package com.heima.exam;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader{
 
 private String classDir;

 public MyClassLoader() {}
 
 public MyClassLoader(String classDir) {
  this.classDir = classDir;
 }
 private static void cypher(InputStream ips, OutputStream ops) throws Exception {
  int b = -1;
  while((b = ips.read()) != -1) {
   ops.write(b ^ 0xff);
  }
 }
 
 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
  // TODO Auto-generated method stub
  String classFileName = classDir + "\\" + name.substring(name.lastIndexOf(".") + 1) + ".class";
  try {
   FileInputStream fis = new FileInputStream(classFileName);
   ByteArrayOutputStream bos = new ByteArrayOutputStream();
   cypher(fis, bos);
   byte[] bytes = bos.toByteArray();
   fis.close();
   bos.close();
   return defineClass(null, bytes, 0, bytes.length);
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return super.findClass(name);
 }

 public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub

  String srcPath = "C:\\D_zone\\workspace_e\\test\\bin\\com\\heima\\exam\\ClassLoaderAttachment.class";
  String destDir = "C:\\D_zone\\workspace_e\\test\\myclass";
  FileInputStream fis = new FileInputStream(srcPath);
  String destPath = destDir + "\\" + srcPath.substring(srcPath.lastIndexOf("\\") + 1);
  FileOutputStream fos = new FileOutputStream(destPath);
  cypher(fis, fos);
  fis.close();
  fos.close();
  
  System.out.println(new ClassLoaderAttachment().toString());
 }

}

//测试方法类
package com.heima.exam;

import java.util.Date;

public class ClassLoaderTest {
 
 public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub

  System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
  System.out.println(System.class.getClassLoader());
  
  ClassLoader loader = ClassLoaderTest.class.getClassLoader();
  while(loader != null) {
   System.out.println(loader.getClass().getName());
   loader = loader.getParent();
  }
  System.out.println(loader);
  
  Class clazz = new MyClassLoader("myclass").loadClass("com.heima.exam.ClassLoaderAttachment");

  /* 我们不能用下面这条语句来声明要创建的类的类型
   * 因为在声明类型的时候,Java自带的类加载器就会来加载这个类,
   * 而我们这个类是经过加密处理的,就会加载失败,
   * 这就是我们在写ClassLoaderAttachment类的时候继承一个父类的原因
   * 那么在类型声明的时候就可以声明为父类的类型。
  ClassLoaderAttachment date = (ClassLoaderAttachment)clazz.newInstance();
  */
  Date date = (Date)clazz.newInstance();
  System.out.println(date);
  System.out.println(date.getClass().getClassLoader().getClass().getName());
 }

}

 

类加载器的一个高级问题的实验分析,一个web项目的Servlet是由tomcat提供的类加载器加载的,
如果我们把一个继承了HttpServlet的Servlet类放到了JRE/lib/ext/目录下,那么这个Servlet就是由ExtClassLoader类加载器加载了,
由于这个Servlet继承了HttpServlet类,所以也需要加载HttpServlet类,但是此时加载HttpServlet类失败,
因为如果类A(发起者)中使用了类B,那么Java虚拟机将使用加载类A的类装载器来加载类B,这时就是一个类加载器的委托机制,
如果父加载器和自己都加载不到类,就会抛出ClassNotFoundException异常,而不会让子加载器去加载类。
而ExtClassLoader这个类加载器只加载JRE/lib/ext/目录下的类,
而HttpServlet类不位于JRE/lib/ext/目录下,所以加载失败,解决办法是把tomcat提供的servlet.jar包放到JRE/lib/ext/目录下,
这时就没问题了,但是此时加载Servlet类的类加载器是ExtClassLoader这个类加载器了,而不是tomcat提供的类加载器了。

0 0
原创粉丝点击