类加载器

实现在虚拟机外部“通过一个类的全限定名来回去描述此类的二进制字节流”的动作的代码,被称之为“类加载器”

类加载器是Java的一项创新,最初是为了满足Java Applet的需求而开发,如今类加载器在类层次划分、OSGi、热部署、代码加密等领域都起到重要作用。

一、类与类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每个类加载器,都拥有一个独立的类名称空间。

比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

上述的“相等”,包括代表类的Class对象的equals()方法isAssignableFrom()方法isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。

import java.io.IOException;
import java.io.InputStream;

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //构造类加载器
        ClassLoader myClassLoader = new ClassLoader(){
            //重写loadClass类
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    //获取全限定类名ClassLoaderTest
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    //被加载类的转字节流
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is==null){
                        return super.loadClass(name);
                    }
                    //根据预估可读字节数读流
                    byte[] bytes = new byte[is.available()];
                    is.read(bytes);
         //将字节数组转换为对应被加载类ClassLoaderTest的实例
                   return defineClass(name,bytes,0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };

        //通过自定义类加载器加载类ClassLoaderTest
        Object obj = myClassLoader.loadClass("ClassLoaderTest").newInstance();

        System.out.println(obj.getClass());
        System.out.println(obj instanceof ClassLoaderTest);
    }
}
运行结果

通过运行结果可以看出,加载的类虽然都是ClassLoaderTest,但是,通过instanceof 进行所属类型检查时却返回了false。这是因为虚拟机中存在了两个 ClassLoaderTest 类,一个是由系统应用程序类加载器加载的,另一个是由自定义的类加载器加载的,虽然来源都是同一个Class文件,但依然是两个独立的类。

二、双亲委派模型

从Java虚拟机的角度而言,只存在两种类加载器:

  1. 启动类加载器(BootStrap ClassLoader),这个类加载器是虚拟机自身的一部分,使用C++实现
  2. 其他所有类加载器,独立于虚拟机之外,都由Java语言实现,都继承自抽象类java.lang.ClassLoader
1)常见的3种系统提供的类加载器
  1. 启动类加载器(BootStrap ClassLoaader):启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null替代即可
  2. 扩展类加载器(Extension ClassLoader):开发者可以直接使用扩展类加载器
  3. 应用程序类加载器(Application ClassLoader):由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称系统类加载器。它负责加载用户类路径(ClassPath)上所制定的类库,开发者可以直接使用这个类加载器,如果程序中没有自定义过类加载器,一般默认使用该加载器
2)什么是双亲委派模型

双亲委派模型(Parents Delegation Model)要求除了顶层的启动类加载器外,其余的类加载器都应用自己的父类加载器。这里的父子关系一般不会以继承(Inheritance)关系来实现,而是使用组合(Composition)关系来复用父加载器

3)双亲委派模型的工作过程

如果一个类加载器收到了类加载的请求,它首先不会尝试自己加载这个类,而是把这个请求委派给父类加载器,每个层次的类加载器都是如此。因此,所有的加载请求最终都应传送到顶层的启动类加载器中,只有当父类加载器反馈无法完成加载请求(它的搜索范围中没有找到所需的类)时,子类才会尝试自己加载。

优点:Java类随类加载器一起具备了层级关系。例如,java.lang.Object类,无论哪个加载器要加载它,最终都将委派给顶端的启动类加载器,因此Object类在程序的各种类加载器环境中都是同一个类,不会出现多个不同的Object类的情况。

上方提供了加载自定义java.lang.Object类,以及自定义的其他以“java.lang”开头的类 的代码。即便使用自定义的类加载器,强行调用defineClass()方法加载“java.lang”开头的自定义类,运行时也不会成功。

加载“java.lang”开头自定义类的运行结果,虽然编译可以通过,但永远无法被加载
4)双亲委派模型的实现

双亲委派模型虽然重要,但是实现起来却较为简单:

先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。

如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

三、破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。Java中大部分的类加载器都遵循这个模型,但也有例外,到目前为止,双亲委派模型出现过3次较大规模的“破坏”

  • jdk1.2之前:由于双亲委派模型在此jdk1.2之后才被引入,而类加载器和抽象类java.lang.ClassLoader则在jdk1.0就存在,为了实现兼容不得不做出妥协。jdk1.2之后不再提倡用户覆盖loadClass()方法
  • 双亲委派模型自身缺陷:为了解决基础类回调用户代码的情况,Java设计团队引入了“线程上下文类加载器(Thread ContextLoader)”。这个类加载器可以通过java.langThread类的setContextClassLoaser()方法设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
  • 追求程序动态性:这里的“动态性”指的是:代码热替换(HotSwap)模块热部署(Hot Deployment)等。最为人熟知的就是OSGi的模块化热部署,在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构,当类收到加载请求时,OSGi将按照以下顺序进行类搜索:
    1. 将以java.*开头的类委派给父类加载器加载
    2. 否则,将委派列表名单内的类委派给父类加载器加载
    3. 否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载
    4. 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
    5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
    6. 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
    7. 否则,类查找失败

以上OSGi查找顺序中只有1,2两点符合双亲委派规则,
其它的类查找都是在平级的类加载器中进行的 。

发表评论

邮箱地址不会被公开。 必填项已用*标注