黑马程序员系列第九篇 类加载器

 

推荐阅读相关书籍《深入java虚拟机》 

目录:1、java.lang.ClassLoader类介绍      2、类加载器的结构     3、加载类的过程     4、自定义类加载器

 

类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。

类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。

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

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

 

1、java.lang.ClassLoader类介绍

java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。

 

2、类加载器的结构

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

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

验证此结构的代码

 1  public class ClassLoaderTree { 
 2 
 3     public static void main(String[] args) { 
//创建类加载器
4 ClassLoader loader = ClassLoaderTree.class.getClassLoader();
//通过while循环,打印父类类加载
5 while (loader != null) { 6 System.out.println(loader.toString()); 7 loader = loader.getParent(); 8 } 9 } 10 }

 

 Java 虚拟机是如何判定两个 Java 类是相同的:

Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。 

 

3、加载类的过程

在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。

方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。

类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。

 

4、自定义类加载器

虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。

自定义一个文件系统类加载器代码

 1  public class FileSystemClassLoader extends ClassLoader { 
 2 
 3     private String rootDir; 
 4      //加载器的构造方法
 5     public FileSystemClassLoader(String rootDir) { 
 6         this.rootDir = rootDir; 
 7     } 
 8        //寻找类
 9     protected Class<?> findClass(String name) throws ClassNotFoundException { 
10         byte[] classData = getClassData(name); 
11         if (classData == null) { 
12             throw new ClassNotFoundException(); 
13         } 
14         else { 
15             return defineClass(name, classData, 0, classData.length); 
16         } 
17     } 
18            //获得类中数据
19     private byte[] getClassData(String className) { 
20         String path = classNameToPath(className); 
21         try { 
22             InputStream ins = new FileInputStream(path); 
23             ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
24             int bufferSize = 4096; 
25             byte[] buffer = new byte[bufferSize]; 
26             int bytesNumRead = 0; 
27             while ((bytesNumRead = ins.read(buffer)) != -1) { 
28                 baos.write(buffer, 0, bytesNumRead); 
29             } 
30             return baos.toByteArray(); 
31         } catch (IOException e) { 
32             e.printStackTrace(); 
33         } 
34         return null; 
35     } 
36           //定义类加载路径
37     private String classNameToPath(String className) { 
38         return rootDir + File.separatorChar 
39                 + className.replace('.', File.separatorChar) + ".class"; 
40     } 
41  }

 

 

 

 

posted @ 2015-11-19 11:36  YerJustDoIt  阅读(189)  评论(0编辑  收藏  举报