Java中的类加载器

来源:互联网 发布:分布式开发 java 编辑:程序博客网 时间:2024/06/06 15:53

Java中的类加载器

  

       类加载器是Java语言的创新,它负责通过类的全限定名找到其对应的字节码,并且从中定义出Java类对象即java.lang.Class的实例。虚拟机的设计者甚至运行开发者定义自己的类加载器,实现自己的类加载逻辑(但是要遵循一定得规则,后面会讲到)。

 

一,双亲委派

        所有的知道类加载器classloader的Java开发者都应该知道它的双亲委派模型。类加载器在加载一个类之前,先尝试通过其父加载器来加载这个类,类似于Spring的BeanFactory。这样做的好处是,父加载器加载过得类如java.lang.Object,子加载器不会再进行加载,保证了应用程序的正确执行。那么,有的人可能有想法,我可以自己写一个java.lang.Object类然后通过自定义的加载器来加载。这样是行不通的,因为虚拟机已经做了限制。下面来分别说说系统中类加载器

 

         1,引导类加载器(bootstrap class loader)

          它用来加载 Java 的核心库,即存放在JAVA_HOME/lib目录中的类,这个目录也可以通过-Xbootclasspath参数指定。但是,虚拟机是识别文件名字的,一个陌生的jar包即使放到启动类的目录中也不会被加载。引导类加载器是用原生代码来实现的,并不继承自java.lang.ClassLoader,也无法被开发者直接引用

 

    2,扩展类加载器(extensions class loader)

          扩展类加载器用来加载 Java 的扩展库,它的实现类是sun.misc.Launcher$ExtClassLoader,它负责加载的目录为JAVA_HOME/lib/ext,同时也可以用java.ext.dirs系统变量指定。

 

    3,系统类加载器(system class loader)

         它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类,其实现类为sun.misc.Launcher$AppClassLoader。一般来说,如果应用程序没有自己实现类加载器的话,Java 应用的类都是由它来完成加载的。我们可以通过ClassLoader.getSystemClassLoader()来获取它。

 

二,线程上下文类加载器

    类加载器的双亲委派模型带来的好处显而易见,但是也有它的局限性。在只能使用父加载器的场景下,无论如何都不能加载到子加载器才能加载到的类。也就是说,如果我们可以打破双亲委派,自己指定系统或当前线程使用哪个类加载器就好了。java.lang.Thread中的方法getContextClassLoader()setContextClassLoader(ClassLoader cl)就可以用来获取和设置线程的上下文类加载器。如果我们没有主动设置的话,线程会从父线程继承原来的类加载器。线程上下文类加载器的典型应用就是JNDI服务。

 

三,实现自己的类加载器

 

    首先,实现自己的类加载器要继承ClassLoader或者其子类。其次,最好不好复写loadClass方法,而是复写findClass方法,因为这样可能破坏双亲委派原则导致系统异常。如果必须要复写loadClass,别忘了基础类还是要通过父加载器进行加载。任何一个类都是Object的类,所以不论是什么类加载器都必须可以直接过间接地加载到Object类。下面是Classloader中方法的说明:

 

方法说明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 类。

 

四,加载资源

 

   ClassLoader不仅可以加载类,同时也可以加载资源。另外,Class对象也可以进行资源的加载,它们的方法都是getResource(String name)和getResourceAsStream(String name)。它们在路径的查找方式上有些不同,如下面代码:

    public static void main(String[] a){        Class clazz = ResourceLoad.class;        ClassLoader classLoader = ResourceLoad.class.getClassLoader();        System.out.println(clazz.getResource(""));        System.out.println(clazz.getResource("/"));        System.out.println(classLoader.getResource(""));        System.out.println(classLoader.getResource("/"));    }


 

     Classloader在加载资源的时候选择的是从classpath目录下开始查找,传入的路径是相对于classpath的,这个方法是可以被重写的。而通过class加载资源,传入的路径是相对于当前类的,或者以“/”开头表示一个绝对的路径(相对于classpath)。getResourceAsStream也是相同的道理。其实class最终也会通过classloader来加载资源,只不过是利用下面的方法做了路径的转换

    /**     * Add a package name prefix if the name is not absolute Remove leading "/"     * if name is absolute     */    private String resolveName(String name) {        if (name == null) {            return name;        }        if (!name.startsWith("/")) {            Class<?> c = this;            while (c.isArray()) {                c = c.getComponentType();            }            String baseName = c.getName();            int index = baseName.lastIndexOf('.');            if (index != -1) {                name = baseName.substring(0, index).replace('.', '/')                    +"/"+name;            }        } else {            name = name.substring(1);        }        return name;    }


 

0 0