[Java5新特性]类加载器

类加载器概述

类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例。一旦一个类被加入JVM中,同一个类就不会被再次加入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

例如以下案例,在JVM中两个同名的Person类是完全不同的,之间也互不兼容,因为类加载器不同。

上述情况转换成代码如下:

  • 定义一个Person类。
public class Person {
    private Person instance;
    public void setPerson(Object instance) {
        this.instance = (Person) instance;
    }
}
  • 编写一个测试方法,用于测试通过两个不同类加载器加载Person类得到不同的Class实例。
    @Test
    public void ClassIdentityTest() throws Exception{
        String classDataRootPath = "WebRoot\\WEB-INF\\classes";
        FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
        FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
        String className = "app.java.classloader.Person";
        try {
            Class<?> class1 = fscl1.findClass(className);
            Object obj1 = class1.newInstance();
            Class<?> class2 = fscl2.findClass(className);
            Object obj2 = class2.newInstance();
            Method setSampleMethod = class1.getMethod("setPerson", java.lang.Object.class);
            setSampleMethod.invoke(obj1, obj2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 上述代码运行后,报如下错误,表示虽然两个对象的类名称相同,但是由于通过不同类加载器得到Class实例,JVM不会认为是相同的。

类加载器分类

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。

  • Bootstrap ClassLoader:根类加载器。
  • Extension ClassLoader:扩展类加载器。
  • System ClassLoader:系统类加载器。

Bootstrap ClassLoader被称为引导(或原始或根)类加载器,它负责加载Java的核心类。在JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。根类加载器非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。

    @Test
    public void BootstrapTest(){
        // 获取根类加载器所加载的全部URL数组
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        // 遍历输出根类加载器加载的全部URL
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i].toExternalForm());
        }
    }

Extension ClassLoader被称为扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或由java.ext.dirs系统属性指定的目录)中的JAR包的类。通过这种方式可以为Java扩展核心类以外的新功能,只要把自己开发的类打包成JAR文件,然后放入%JAVA_HOME%/jre/lib/ext路径即可。

    @Test
    public void ExtensionTest() throws Exception{
        // 位于jre/lib/ext/dnsns.jar
        DNSNameService dnsNameService = new DNSNameService();
        System.out.println(dnsNameService.getClass().getClassLoader());
    }

System ClassLoader被称为系统或应用类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类径路。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以系统类加载器作为父加载器。

    @Test
    public void SystemTest() throws Exception{
        // 获取当前类的实例对象
        ClassLoaderTest classLoaderTest = new ClassLoaderTest();
        System.out.println(classLoaderTest.getClass().getClassLoader());
    }

以下是JVM中4种类加载器的层次结构:

    @Test
    public void ClassLoaderTreeTest() throws Exception{
        // 获取当前类的类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        // 判断类加载器是否为空
        while (classLoader != null) {
            // 打印当前类加载器的名称
            System.out.print(classLoader.getClass().getName()+"->");
            // 获取当前类加载器的父级
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);
    }

上述代码中可以看出JVM中类加载器的层次结构。最后输出的是null的愿意是因为根类加载器并不是Java提供的,而是JVM提供的,所以不能获取。

类加载机制

JVM的类加载器机制主要有如下三种:

  • 全盘负责。所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
  • 父类委托。所谓父类委托,则是先让父类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  • 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这也是为什么修改Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

自定义类加载器

JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,可以通过扩展ClassLoader的子类,并重写该ClassLoader提供的方法来实现自定义的类加载器。

ClassLoader类具有如下两个关键方法:

  • loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
  • findClass(String name):根据指定名称来查找类。

由于loadClass()方法的执行步骤为1)利用findLoadedClass()方法来检查是否已经加载类。2)在父类加载器调用loadClass()方法。3)调用findClass()方法查找类。重写findClass()方法可以避免默认类加载器的父类委托和缓冲机制,这里推荐重写findClass()方法。

下面来实现一个文件系统类加载器,用于加载存储在文件系统上的Java字节代码。

public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace(‘.‘, File.separatorChar) + ".class";
    }
}


转载说明:请注明作者及原文链接,谢谢!

时间: 2024-11-06 23:37:00

[Java5新特性]类加载器的相关文章

[Java5新特性] 动态代理

动态代理概述 代理模式是Java设计模式中的一种,其特征为代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现业务,而是通过调用委托类对象的相关方法来提供具体业务. 在Java中的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和接口可以生成JDK动态代理或动态代理对

Java5新特性

"JDK1.5/Java5"的一个重要主题就是通过新增一些特性来简化开发. 这些特性包括泛型,for-each循环,自动装包/拆包,枚举,可变参数, 静态导入,注解. 使用这些特性有助于我们编写更加清晰,精悍,安全的代码. 1.泛型(Generic)C++通过模板技术可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能.一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们进行强制得类型转换.猛虎引入了泛型,它允许指定集合里元素的类型,这样你可以得到

Java5新特性之静态导入、可变参数、增强for循环、自动拆装箱

JDK1.5已经发布很长时间,之所以还拿出来是因为它增加了很多个重要的特性,使用这些特性有助于我们简化开发,编写的代码更加简洁清晰安全,主要有以下几个特性: ?  静态导入 ?  可变参数 ?  增强for循环 ?  自动拆装箱 ? 泛型 ? 枚举 由于泛型.枚举内容比较多,也最重要,之后单拿出来讲.这里先介绍前面四个简单而又实用的小特性. 1. 静态导入 所谓"静态导入"只不过是在普通的import语句中加入关键字static,例如: ?  非静态导入:import java.lan

[Java5新特性]Annotation注解

Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包.类.构造器.方法.成员变量.参数和局部变量的声明,这些信息被存储在Annotation的"name=value"对中. Annotation能被用来为程序元素(类.方法.成员变量等)设置元数据,比如一段代码的作者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行. 基本A

java5 新特性

1.静态导入方法 Java代码   package com.java.new_features_jdk5; /** * * 一般我们导入一个类都用 import com.....ClassName;而静态导入是这样:import static com.....ClassName.*; * 这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法.当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了. * 然后在这个类中,就可以直接用方

[Java5新特性]自动装箱/拆箱

自动装箱/拆箱概述 Java中具有基本类型(int,double,float,long,boolean,char,byte,short)和基本类型包装类(Integer,Double,Float,Long,Boolean,Char,Byte,Short),我们实现基本类型与包装类之间的转换基本有两种方式: 一种为JDK5之前的方式,比如Integer i = Integer.valueof(5);(这里5为基本类型int,Integer包装类利用valueof()方法将其转换为Integer类型

[Java5新特性]可变参数

什么是可变参数 Java基础内容中,关于函数具有一种特性:重载,如果我们要完成多个数字想加的需求,可以按照以下代码完成: public class Demo { public int add(int a, int b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } public static void main(String[] args) { int sum1 = new Demo().a

[Java5新特性]加强For循环

替换迭代器 我们先来回忆一下Java中的迭代器的用法,可以使用迭代器的有List和Set集合.原因在于它们都实现了Collection接口,而Collection接口拥有一个叫做Iterable父接口.下面我们来看一个案例: public class Demo { @Test public void demo() { List<String> list = new ArrayList<String>(); list.add("hello"); list.add(

[Java5新特性]泛型

Java中集合的问题 Java中的集合有个缺点:就是当我们把数据放置到集合中时,集合是不会记住数据类型的.也就是说,当我们再从集合中获取到数据时,数据类型都变成了Object类型了. 换句话讲,集合对元素类型是没有任何限制的.这样可能会出现一些问题,例如如果我们要创建一个专门存储字符串的List集合的话,也可以将Integer类型数据放置进入.即使放置进去的都是字符串数据,从List集合取出时,还是需要类型转换的(因为集合中元素类型都是Object类型). 例如下面这个例子:创建一个只保存字符串