什么是类加载器
- 基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例
- 也可以加载图片等应用所需的资源
首先关注:java的内置类加载器的几种类型
- 启动类加载器(Bootstrap ClassLoader):这个类加载器是Java虚拟机本身的一部分,这个类将负责存放
\lib 目录下的类。要注意的一点是,这个类加载器是无法被用户直接引用的。并不继承自 java.lang.ClassLoader - 扩展类加载器(Extention ClassLoader):这个类加载器是由sun.misc.Launcher$ExtClassLoader实现的,负责加载
\lib\ext 目录中class。开发者可以直接使用扩展类加载器 - 应用程序类加载器(Application ClassLoader):这个类加载器是有sun.misc.Launcher$AppClassLoader实现的。这个类加载器是系统默认的类加载器。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类(在命令行中可以直接使用-cp或者-classpath命令进行指定)。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它
也就是说如果我们希望取代默认的类加载器,需要自己实现classloader,并且往往需要违反委托模型。违反委托模型的用意在于,不让父加载器拦截类,从而让类顺利的被我们的classLoader加载。
委派模型
具体参见: https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
几点注意:
- 真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器
一个类的定义加载器是它引用的其他类的初始加载器,如Outer引用了类Inner,则由Outer的定义加载器负责启动Inner的加载过程。 - loadClass()抛出的异常:java.lang.ClassNotFoundExeption
- defineClass()抛出的异常:java.lang.ClassDefFoundError
- 自定义的类加载器都“继承“自AppClassLoader。当然在实现上是继承java.lang.ClassLoader
- 类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来
实现一个自己的加载器
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
注意点:
- 覆写findClass方法,而不是loadClass方法
- 读取is的时候,不要依赖不确定的available()方法返回不一定正确的buffer大小
- 要传入类的全限定名称
注意:在测试的时候,如果试图加载的类本身处于classPath下的话,覆写findClass并不会改变双亲委派模型,因此仍然会被AppClassLoader拦截。要测试不同的classLoader加载的类不一样,必须让appClassLoader不能拦截(通过加载URL访问的某个类或不在classPath下面的类)
线程上下文类加载器
主要用于解决类加载委托机制不能工作的情况:
Java提供的很多服务提供者接口(Service Provider Interface, SPI),允许第三方为这些接口提供实现。例如:JDBC、JCE、JNDI、JAXP等。
这里的问题在于,接口由rt.jar核心类库提供,而实现则是由第三方提供。
SPI接口代码经常需要加载具体的实现类,而核心的接口由BootstrapClassLoader加载,具体实现则是在classPath类路径下面,由appClassLoader加载。由上面的委托机制可知,由BootstrapClassLoader是无法引用AppClassLoader的(等于是反向委托),因此这种单向的委托无法完成需求。
而线程上下文类加载器则解决了这个问题,等于是一种”犯规“,如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器(AppClassLoader),从而利用这种”作弊“可以让BootstrapClassLoader反向委托AppClassLoader去加载实现类。
基本上涉及SPI的加载,都是通过这种方法来完成的。
类加载器与 OSGi
OSGI是java自定义类加载器机制的一个典型应用。
OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。
OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation的值即可。
参考
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
深入探索Java热部署