https://www.ibm.com/developerworks/cn/java/j-lo-classloader/java
用来加载 Java 类到 Java 虚拟机中数据库
Java 源程序(.java 文件)在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class
类的一个实例;每一个这样的实例用来表示一个 Java 类。经过此实例的 newInstance()
方法就能够建立出该类的一个对象。apache
基本上全部的类加载器都是 java.lang.ClassLoader
类的一个实例。bootstrap
java.lang.ClassLoader
类java.lang.Class
类的一个实例;方法 | 说明 |
---|---|
getParent() |
返回该类加载器的父类加载器。 |
loadClass(String name) |
加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findClass(String name) |
查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findLoadedClass(String name) |
查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。 |
defineClass(String name, byte[] b, int off, int len) |
把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。 |
resolveClass(Class<?> c) |
连接指定的 Java 类。 |
表示类名称的 name
参数的值是类的二进制名称。须要注意的是内部类的表示,如 com.example.Sample$1
和 com.example.Sample$Inner
等表示方式。数组
Java 中的类加载器大体能够分红两类,一类是系统提供的,另一类则是由 Java 应用开发人员编写的。安全
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
。网络
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。工具
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。通常来讲,Java 应用的类都是由它来完成加载的。能够经过 ClassLoader.getSystemClassLoader()
来获取它。spa
java.lang.ClassLoader
类的方式实现本身的类加载器,以知足一些特殊的需求。 除了引导类加载器以外,全部的类加载器都有一个父类加载器。经过 getParent()
方法能够获得。线程
对于系统提供的类加载器来讲,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;
对于开发人员编写的类加载器来讲,其父类加载器是加载此类加载器 Java 类的类加载器。
由于类加载器 Java 类如同其它的 Java 类同样,也是要由类加载器来加载的。通常来讲,开发人员编写的类加载器的父类加载器是系统类加载器。
public class ClassLoaderTree { public static void main(String[] args) { ClassLoader loader = ClassLoaderTree.class.getClassLoader(); //有些 JDK 的实现对于父类加载器是引导类加载器的状况,getParent()方法返回 null while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } } //输出 sun.misc.Launcher$AppClassLoader@9304b1 //系统类加载器实例 sun.misc.Launcher$ExtClassLoader@190d11 //扩展类加载器实例
类加载器在尝试本身去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。
Java 虚拟机不只要看类的全名是否相同,还要看加载此类的类加载器(定义加载器)是否同样。只有二者都相同的状况,才认为两个类是相同的
经过代理模式,对于 Java 核心库的类的加载工做由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
不一样类加载器加载的类之间是不兼容的,这就至关于在 Java 虚拟机内部建立了一个个相互隔离的 Java 类空间。
加载 + 启动加载
defineClass
来实现的;而启动类的加载工过程是经过调用加载器的 loadClass
来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。
在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。
类 java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用来获取和设置线程的上下文类加载器。
若是没有经过 setContextClassLoader(ClassLoader cl)
方法进行设置的话,线程将继承其父线程的上下文类加载器。
Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码能够经过此类加载器来加载类和资源。
该方法有两种形式:
Class.forName(String name, boolean initialize, ClassLoader loader)
和
Class.forName(String className)
。
第一种形式的参数 name
表示的是类的全名;initialize
表示是否初始化类;loader
表示加载时使用的类加载器。
第二种形式则至关于设置了参数 initialize
的值为 true
,loader
的值为当前类的类加载器。
Class.forName
的一个很常见的用法是在加载数据库驱动的时候
如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() //
用来加载 Apache Derby 数据库的驱动。
文件系统类加载器
加载存储在文件系统上的 Java 字节代码,经过 类 FileSystemClassLoader 的
defineClass()
方法来把这些字节代码转换成 java.lang.Class
类的实例。
类 NetworkClassLoader
负责经过网络下载 Java 类字节代码并定义出 Java 类。
每一个 Web 应用都有一个对应的类加载器实例。
该类加载器也使用代理模式,所不一样的是它是首先尝试去加载某个类,若是找不到再代理给父类加载器。这与通常类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐作法,其目的是使得 Web 应用本身的类的优先级高于 Web 容器提供的类。
这种代理模式的一个例外是:Java 核心库的类是不在查找范围以内的。这也是为了保证 Java 核心库的类型安全。
每一个 Web 应用本身的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes
和 WEB-INF/lib
目录下面。
多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由全部 Web 应用共享的目录下面。
当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
OSGi™是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。
OSGi 中的每一个模块都有对应的一个类加载器。它负责加载模块本身包含的 Java 包和类。当它须要加载 Java 核心库的类时(以 java
开头的包和类),它会代理给父类加载器(一般是启动类加载器)来完成。当它须要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。
若是一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在 Bundle-ClassPath
中指明便可。
若是一个类库被多个模块共用,能够为这个类库单独的建立一个模块,把其它模块须要用到的 Java 包声明为导出的。其它模块声明导入这些类。
若是类库提供了 SPI 接口,而且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。若是出现了 NoClassDefFoundError
异常,首先检查当前线程的上下文类加载器是否正确。经过 Thread.currentThread().getContextClassLoader()
就能够获得该类加载器。该类加载器应该是该模块对应的类加载器。若是不是的话,能够首先经过 class.getClassLoader()
来获得模块对应的类加载器,再经过 Thread.currentThread().setContextClassLoader()
来设置当前线程的上下文类加载器。