理解ClassLoader

        当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

       bootstrap classloader
                 |
        extension classloader
                 |
        system classloader

        bootstrap classloader 引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 -D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:

 URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    for (int i = 0; i < urls.length; i++) {
      System.out.println(urls.toExternalForm());
    }

我的计算机上的结果如下:

file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/resources.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/rt.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/sunrsasign.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jsse.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jce.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/charsets.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jfr.jar
file:/usr/lib/jvm/jdk1.8.0_72/jre/classes

        extension classloader - 扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类 包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:

 System.out.println(System.getProperty("java.ext.dirs"));
    ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
    System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());

结果为:

/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext:/usr/java/packages/lib/ext
the parent of extension classloader : null

        extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。

        system classloader - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法

ClassLoader.getSystemClassLoader()

找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:

   System.out.println(System.getProperty("java.class.path"));

输出结果则为用户在系统属性里面设置的CLASSPATH。

/usr/lib/jvm/jdk1.8.0_72/jre/lib/charsets.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/deploy.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/dnsns.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/jaccess.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/jfxrt.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/localedata.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/nashorn.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/sunec.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/ext/zipfs.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/javaws.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jce.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jfr.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jfxswt.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/jsse.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/management-agent.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/plugin.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/resources.jar:/usr/lib/jvm/jdk1.8.0_72/jre/lib/rt.jar:/home/dep/Commonservice/target/classes:/root/.m2/repository/org/apache/kafka/kafka_2.11/0.10.0.0/kafka_2.11-0.10.0.0.jar:/root/.m2/repository/com/101tec/zkclient/0.8/zkclient-0.8.jar:/root/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar:/root/.m2/repository/com/yammer/metrics/metrics-core/2.2.0/metrics-core-2.2.0.jar:/root/.m2/repository/org/scala-lang/scala-library/2.11.8/scala-library-2.11.8.jar:/root/.m2/repository/org/scala-lang/modules/scala-parser-combinators_2.11/1.0.4/scala-parser-combinators_2.11-1.0.4.jar:/root/.m2/repository/org/apache/kafka/kafka-clients/0.10.0.0/kafka-clients-0.10.0.0.jar:/root/.m2/repository/net/jpountz/lz4/lz4/1.3.0/lz4-1.3.0.jar:/root/.m2/repository/org/xerial/snappy/snappy-java/1.1.2.4/snappy-java-1.1.2.4.jar:/root/.m2/repository/net/sf/jopt-simple/jopt-simple/4.9/jopt-simple-4.9.jar:/root/.m2/repository/org/apache/zookeeper/zookeeper/3.4.6/zookeeper-3.4.6.jar:/root/.m2/repository/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar:/root/.m2/repository/log4j/log4j/1.2.16/log4j-1.2.16.jar:/root/.m2/repository/jline/jline/0.9.94/jline-0.9.94.jar:/root/.m2/repository/junit/junit/3.8.1/junit-3.8.1.jar:/root/.m2/repository/io/netty/netty/3.7.0.Final/netty-3.7.0.Final.jar:/home/idea-IC-163.12024.16/lib/idea_rt.jar

        classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必 须重新启动JVM才能生效的原因。
每个ClassLoader加载Class的过程是:

  1. 检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到 2
  2. 如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
  3. 请求parent classloader载入,如果成功到8,不成功到5
  4. 请求jvm从bootstrap classloader中载入,如果成功到8
  5. 寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
  6. 从文件中载入Class,到8.
  7. 抛出ClassNotFoundException.
  8. 返回Class.

其中5.6步可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。

类加载器的顺序是:

        先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类, 也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:

 System.out.println(System.class.getClassLoader());

将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。

        下面就让来看看JVM是如何来为来建立类加载器的结构的:

sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下来代码:

   System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());

结果为:

  the Launcher's classloader is null

(因为是用bootstrap classloader加载,所以class loader为null)Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。

        让来看看Launcher初试化的过程的部分代码。

        Launcher的部分代码:

  public class Launcher  {
     public Launcher() {
          ExtClassLoader extclassloader;
          try {
              //初始化extension classloader
              extclassloader = ExtClassLoader.getExtClassLoader();
          } catch(IOException ioexception) {
             throw new InternalError("Could not create extension class loader");
         }
         try {
            //初始化system classloader,parent是extension classloader
             loader = AppClassLoader.getAppClassLoader(extclassloader);
         } catch(IOException ioexception1) {
            throw new InternalError("Could not create application class loader");
         }
        //将system classloader设置成当前线程的context classloader(将在后面加以介绍)
        Thread.currentThread().setContextClassLoader(loader);   
    }
    public ClassLoader getClassLoader() {
        //返回system classloader
        return loader;
     }
 }

        extension classloader的部分代码:

  static class Launcher$ExtClassLoader extends URLClassLoader {
 public static Launcher$ExtClassLoader getExtClassLoader()
        throws IOException
     {
         File afile[] = getExtDirs();
         return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
     }
    private static File[] getExtDirs() {
        //获得系统属性“java.ext.dirs”
        String s = System.getProperty("java.ext.dirs");
        File afile[];
        if(s != null) {
             StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
            int i = stringtokenizer.countTokens();
            afile = new File;
            for(int j = 0; j < i; j++)
                afile[j] = new File(stringtokenizer.nextToken());
       } else {
            afile = new File[0];
      }
     return afile;
   }
}

system classloader的部分代码:

  static class Launcher$AppClassLoader extends URLClassLoader
  {
     public static ClassLoader getAppClassLoader(ClassLoader classloader)
          throws IOException
     {
          //获得系统属性“java.class.path”
         String s = System.getProperty("java.class.path");
         File afile[] = s != null ? Launcher.access$200(s) : new File[0];
        return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
    }
}

        extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。

        context classloader在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来 指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入所需要的Class。默认的是system classloader。利用这个特性,可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例 如web应用中的servlet就是用这种机制加载的.

        好了,现在了解了classloader的结构和工作原理,那么 如何实现在运行时的动态载入和更新呢?只要能够动态改变类搜索路径和清除classloader的cache中已经载入的Class就行了,有两个方 案,一是继承一个classloader,覆盖loadclass方法,动态的寻找Class文件并使用defineClass方法来;另一个则非常简单实用,只要重新使用一个新的类搜索路径来new一个classloader就行了,这样即更新了类搜索路径以便来载入新的Class,也重新生成了一 个空白的cache(当然,类搜索路径不一定必须更改)。
java.netURLClassLoader正是一个符 合要求的classloader!可以直接使用或者继承它就可以了!

这是j2se1.4 API的doc中URLClassLoader的两个构造器的描述:

URLClassLoader(URL[] urls)
          Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
URLClassLoader(URL[] urls, ClassLoader parent)
          Constructs a new URLClassLoader for the given URLs.

其中URL[] urls就是要设置的类搜索路径,parent就是这个classloader的parent classloader,默认的是system classloader。

好,现在能够动态的载入Class了,这样就可以利用newInstance方法来获得一个Object。但如何将此Object造型呢?可以将此Object造型成它本身的Class吗?

首先让来分析一下java源文件的编译,运行吧!javac命令是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法来编译:

    public static int compile(String as[]);
    public static int compile(String as[], PrintWriter printwriter);

返回0表示编译成功,字符串数组as则是用javac命令编译时的参数,以空格划分。例如:

javac -classpath c:"foo"bar.jar;. -d c:" c:"Some.java

则 字符串数组as为

{"-classpath","c:""foo""bar.jar;.","-d","c:""","c:""Some.java"}

, 如果带有PrintWriter参数,则会把编译信息出到这个指定的printWriter中。默认的输出是System.err。

其中 Main是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入,如果system classloader不能载入某个Class时,编译器将抛出一个“cannot resolve symbol”错误。

        所以首先编译就通不过,也就是编译器无法编译一个引用了不在CLASSPATH中的未知Class的java源文件,而由于拼写错误或者没有把所需类库放到CLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧!

        其 次,就是把这个Class放到编译路径中,成功的进行了编译,然后在运行的时候不把它放入到CLASSPATH中而利用自己的 classloader来动态载入这个Class,这时候也会出现“java.lang.NoClassDefFoundError”的违例,为什么呢?

        再来分析一下,首先调用这个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,当进行造型的时候,JVM也会使用system classloader来尝试载入这个Class来对实例进行造型,自然在system classloader寻找不到这个Class时就会抛出“java.lang.NoClassDefFoundError”的违例。

        OK, 现在让来总结一下,java文件的编译和Class的载入执行,都是使用Launcher初始化的system classloader作为类载入器的,无法动态的改变system classloader,更无法让JVM使用自己的classloader来替换system classloader,根据全盘负责原则,就限制了编译和运行时,无法直接显式的使用一个system classloader寻找不到的Class,即只能使用Java核心类库,扩展类库和CLASSPATH中的类库中的Class。

        还不死心!再尝试一下这种情况,把这个Class也放入到CLASSPATH中,让system classloader能够识别和载入。然后通过自己的classloader来从指定的class文件中载入这个Class(不能够委托 parent载入,因为这样会被system classloader从CLASSPATH中将其载入),然后实例化一个Object,并造型成这个Class,这样JVM也识别这个Class(因为 system classloader能够定位和载入这个Class从CLASSPATH中),载入的也不是CLASSPATH中的这个Class,而是从 CLASSPATH外动态载入的,这样总行了吧!十分不幸的是,这时会出现“java.lang.ClassCastException”违例。

        为 什么呢?也来分析一下,不错,虽然从CLASSPATH外使用自己的classloader动态载入了这个Class,但将它的实例造型的时 候是JVM会使用system classloader来再次载入这个Class,并尝试将使用的自己的classloader载入的Class的一个实例造型为system classloader载入的这个Class(另外的一个)。大家发现什么问题了吗?也就是尝试将从一个classloader载入的Class的一 个实例造型为另外一个classloader载入的Class,虽然这两个Class的名字一样,甚至是从同一个class文件中载入。但不幸的是JVM 却认为这个两个Class是不同的,即JVM认为不同的classloader载入的相同的名字的Class(即使是从同一个class文件中载入的)是 不同的!这样做的原因我想大概也是主要出于安全性考虑,这样就保证所有的核心Java类都是system classloader载入的,无法用自己的classloader载入的相同名字的Class的实例来替换它们的实例。

        那就是利用面向对象的基本特性之一的多形性。把动态载入的Class的实例造型成它的一个system classloader所能识别的父类就行了!这是为什么呢?还是要再来分析一次。当用自己的classloader来动态载入这只要把 这个Class的时候,发现它有一个父类Class,在载入它之前JVM先会载入这个父类Class,这个父类Class是system classloader所能识别的,根据委托机制,它将由system classloader载入,然后的classloader再载入这个Class,创建一个实例,造型为这个父类Class,注意了,造型成这个父类 Class的时候(也就是上溯)是面向对象的java语言所允许的并且JVM也支持的,JVM就使用system classloader再次载入这个父类Class,然后将此实例造型为这个父类Class。大家可以从这个过程发现这个父类Class都是由 system classloader载入的,也就是同一个class loader载入的同一个Class,所以造型的时候不会出现任何异常。而根据多形性,调用这个父类的方法时,真正执行的是这个Class(非父类 Class)的覆盖了父类方法的方法。这些方法中也可以引用system classloader不能识别的Class,因为根据全盘负责原则,只要载入这个Class的classloader即自己定义的 classloader能够定位和载入这些Class就行了。

这样就可以事先定义好一组接口或者基类并放入CLASSPATH中,然 后在执行的时候动态的载入实现或者继承了这些接口或基类的子类。还不明白吗?让来想一想Servlet吧,web application server能够载入任何继承了Servlet的Class并正确的执行它们,不管它实际的Class是什么,就是都把它们实例化成为一个Servlet Class,然后执行Servlet的init,doPost,doGet和destroy等方法的,而不管这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)动态载入。说了这么多希望大家都明白了。在applet,ejb等 容器中,都是采用了这种机制.

对于以上各种情况,希望大家实际编写一些example来实验一下。

最后我再说点别 的,classloader虽然称为类加载器,但并不意味着只能用来加载Class,还可以利用它也获得图片,音频文件等资源的URL,当然,这些资 源必须在CLASSPATH中的jar类库中或目录下。来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描述吧:

public URL getResource(String name)

用指定的名字来查找资源,一个资源是一些能够被class代码访问的在某种程度上依赖于代码位置的数据(图片,音频,文本等等)。
一个资源的名字是以’/’号分隔确定资源的路径名的。
这个方法将先请求parent classloader搜索资源,如果没有parent,则会在内置在虚拟机中的classloader(即bootstrap classloader)的路径中搜索。如果失败,这个方法将调用findResource(String)来寻找资源。

public static URL getSystemResource(String name)

        从用来载入类的搜索路径中查找一个指定名字的资源。这个方法使用system class loader来定位资源。即相当于

ClassLoader.getSystemClassLoader().getResource(name)。

例如:

System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));

的结果为:

jar:file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class

        表明String.class文件在rt.jar的java/lang目录中。
        因此可以将图片等资源随同Class一同打包到jar类库中(当然,也可单独打包这些资源)并添加它们到class loader的搜索路径中,就可以无需关心这些资源的具体位置,让class loader来帮寻找了!

        最后 附上自己的几点理解

bootstrap classloader ——- 对应jvm中某c++写的dll类
Extenson ClassLoader ———对应内部类ExtClassLoader
System ClassLoader ———对应内部类AppClassLoader
Custom ClassLoader ———-对应任何URLClassLoader的子类
以上四种classloder按照从上到下的顺序,依次为下一个的parent

这个第一概念
第二个概念是几个有关的classloader的类

          抽象类 ClassLoader
                   |
            SecureClassLoader
                   |
            URLClassloader
             |           |                
 sun的ExtClassLoader   sun的AppClassLoader

以上的类之间是继承关系,与第一个概念说的parent是两回事情,需要小心。

第三个概念是Thread的ContextClassLoader

其实从Context的名称就可以看出来,这只是一个用以存储任何classloader引用的临时存储空间,与classloader的层次没有任何关系。
第四 就是如何实现自己的classloader了

问题:在何种情形下使用thread.getcontextclassloader()?

        尽管没经常遇到这个问题,但是想获得准确的答案并不那么容易,特别是在开发应用框架的时候,你需要动态的加载一些类和资源,不可避免的你会被此困扰。一般来说,动态载入资源有三种ClassLoader可以选择,System ClassLoader(也叫App ClassLoader)、Current的ClassLoader和CurrentThread的Context ClassLoader。那么, 如何选择使用?

        首先可以简单排除的是System ClassLoader,这个ClassLoader负责从参数-classpath、-cp和操作系统CLASSPATH中载入资源。并且,任何ClassLoader的getSystemXXX()方法都是有以上几个路径指定的。应该很少需要编写直接使用ClassLoader的程序,否则你的代码将只能在命令行运行,发布你的代码成为ejb、web应用或者java web start应用,我肯定他们会崩溃!

        接下来,只剩下两个选择了:Current ClassLoader和Thread Context ClassLoader

Current ClassLoader:当前类所属的ClassLoader,在虚拟机中类之间引用,默认就是使用这个ClassLoader。另外,当你使用Class.forName(), Class.getResource()这几个不带ClassLoader参数的方法是,默认同样适用当前类的ClassLoader。你可以通过方法XX.class.GetClassLoader()获取。

Thread Context ClassLoader,没一个Thread有一个相关联系的Context ClassLoader(由native方法建立的除外),可以通过Thread.setContextClassLoader()方法设置。如果你没有主动设置,Thread默认集成Parent Thread的 Context ClassLoader(注意,是parent Thread 不是父类)。如果 你整个应用中都没有对此作任何处理,那么 所有的Thread都会以System ClassLoader作为Context ClassLoader。知道这一点很重要,因为从web服务器,java企业服务器使用一些复杂而且精巧的ClassLoader结构去实现诸如JNDI、线程池和热部署等功能以来,这种简单的情况越发的少见了。

这篇文章中为什么把Thread Context ClassLoader放在首要的位置,别人并没有大张旗鼓的介绍它?很多开发者都对此不甚了解,因为sun没有提供很好的说明文档。

事实上,Context ClassLoader提供一个突破委托代理机制的后门。虚拟机通过父子层次关系组织管理ClassLoader,每一个ClassLoader都有一个Parent ClassLoader(BootStartp不在此范围之内),当要求一个ClassLoader装载一个类时,他首先请求Parent ClassLoader去装载,只有parent ClassLoader装载失败,才会尝试自己装载。

但是,某些时候这种顺序机制会造成困扰,特别是jvm需要动态载入有开发者提供的资源时。就以JNDI为例,JNDI的类是由bootstarp ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent可以通过获得当前调用Thread的方法获得调用线程的Context ClassLoder 来载入类。

介绍完这些之后,走到的十字路口,任一选择都不是万能的。一些人认为Context ClassLoader将会是新的标准。但是 一旦你的多线程需要通讯某些共享数据,你会发现,你将有一张极其丑陋的ClassLoader分布图,除非所有的线程使用一样的Context ClassLoader。并且委派使用当前ClassLoder对一些方法来说是默认继承来的,比如说Class.forName()。尽管你明确的在任何你能控制的地方使用Context ClassLoader,但是毕竟还有很多代码不归你管(备注:想起一个关于UNIX名字来源的笑话)。

某些应用服务器使用不同的ClassLoder作为Context ClassLoader和当前ClassLoader,并且这些ClassLoader有着相同的ClassPath,但没有父子关系,这使得情况更复杂。请列位看官,花几秒钟时间想一想,为什么这样不好?被载入的类在虚拟机内部有一个全名称,不同的ClassLoader载入的相同名称的类是不一样的,这就隐藏了类型转换错误的隐患。(注:奶奶的 俺就遇到过,JBOSSClassLoader机制蛮挫的)
这种混乱事实上在java类中也有,试着去猜测任何一个包含动态加载的java规范的ClassLoader机制,以下是一个清单:

 JNDI uses context classloaders
    Class.getResource() and Class.forName() use the current classloader
    JAXP uses context classloaders (as of J2SE 1.4)
    java.util.ResourceBundle uses the caller's current classloader
    URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
    Java Serialization API uses the caller's current classloader by default

        而且关于这些资源的类加载机制文档时很少。

        java开发人员应该怎么做?

如果你的实现是利用特定的框架,那么恭喜你,实现它远比实现框架要简单得多!例如,在web应用和EJB应用中,你仅仅只要使用 Class.getResource()就足够了。

其他的情形下,俺有个建议(这个原则是俺工作中发现的,侵权必究,抵制盗版。),

下面这个类可以在整个应用中的任何地方使用,作为一个全局的ClassLoader(所有的示例代码可以从download下载):

  public abstract class ClassLoaderResolver {
public static synchronized ClassLoader getClassLoader() {
final Class caller = getCallerClass(0);
final ClassLoadContext ctx = new ClassLoadContext(caller);

return s_strategy.getClassLoader(ctx);
}

 public static synchronized IClassLoadStrategy getStrategy() {
return s_strategy;
}

public static synchronized IClassLoadStrategy setStrategy(
 final IClassLoadStrategy strategy) {
 final IClassLoadStrategy old = s_strategy;
s_strategy = strategy;
return old;
 }


private static final class CallerResolver extends SecurityManager {
 protected Class[] getClassContext() {
return super.getClassContext();
} } // End of nested class

private static Class getCallerClass(final int callerOffset) {
return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET
 + callerOffset];
}
 private static IClassLoadStrategy s_strategy; // initialized in <clinit>
 private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if
 // this class is
// redesigned private static final CallerResolver CALLER_RESOLVER; // set in <clinit>

static {
 try {
// This can fail if the current SecurityManager does not allow
 // RuntimePermission ("createSecurityManager"):

CALLER_RESOLVER = new CallerResolver();
} catch (SecurityException se) {
 throw new RuntimeException(
"ClassLoaderResolver: could not create CallerResolver: "
 + se);
}

 s_strategy = new DefaultClassLoadStrategy();
}
} // End of class.

通过ClassLoaderResolver.getClassLoader()方法获得一个ClassLoader的引用,并且利用正常的ClassLoader的api去加载资源,你也可以使用 ResourceLoader API作为备选方案

  public abstract class ResourceLoader {
  public static Class loadClass (final String name)throws ClassNotFoundException{
 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
 return Class.forName (name, false, loader);
  }
public static URL getResource (final String name){
 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
if (loader != null)return loader.getResource (name);
else return ClassLoader.getSystemResource (name);
} more methods 

} // End of class

而决定使用何种ClassLoader策略是由接口实现的,这是一种插件机制,方便变更。

public interface IClassLoadStrategy{
ClassLoader getClassLoader (ClassLoadContext ctx);
} // End of interface

它需要一个ClassLoader Context 对象去决定使用何种ClassLoader策略。

  public class ClassLoadContext{
  public final Class getCallerClass (){
 return m_caller;
}
ClassLoadContext (final Class caller){
  m_caller = caller;

}

 private final Class m_caller;

 } // End of class

ClassLoadContext.getCallerClass()返回调用者给ClassLoaderResolver 或者 ResourceLoader,因此能获得调用者的ClassLoader。需要注意的是,调用者是不会变的 (注:作者使用的final修饰字)。俺的方法不需要对现有的业务方法做扩展,而且可以作为静态方法是用。而且,你可以根据自己的业务场景实现独特的ClassLoaderContext。

看出来没,这是一种很熟悉的设计模式,XD ,把获得ClassLoader的策略从业务中独立出来,这个策略可以是”总是用ContextClassLoader”或者”总是用当前ClassLoader”。想预先知道那种策略是正确的比较困难,那么这种模式可以让你简单的改变策略。

俺写了一个默认的实现,基本可以对付95%的场景(enjoy yourself)

 public class DefaultClassLoadStrategy implements IClassLoadStrategy{
  public ClassLoader getClassLoader (final ClassLoadContext ctx){
final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();
final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
  ClassLoader result;
if (isChild (contextLoader, callerLoader))result = callerLoader; else if (isChild (callerLoader, contextLoader))result = contextLoader;
else{
// This else branch could be merged into the previous one,
// but I show it here to emphasize the ambiguous case:
 result = contextLoader;
}
final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();

// Precaution for when deployed as a bootstrap or extension class:
 if (isChild (result, systemLoader))result = systemLoader;
 return result;
}
 more methods 
} // End of class

上面的逻辑比较简单,如果当前ClassLoader和Context ClassLoader是父子关系,那就总选儿子,根据委托原则,这个很容易理解。

如果两人平级,选择正确的ClassLoader很重要,运行时不允许含糊。这种情况下,我的代码选择Context ClassLoader(这是俺个人的经验之谈),当然也不要担心不能改变,你能随便根据需要改变。一般而言,Context ClassLoader比较适合框架,而Current ClassLoader在业务逻辑中用的更多。

最后,检查确保选中的ClassLoader不是System ClassLoader的parent,一旦高于System ClassLoader ,请使用System ClassLoader(你的类部署在Ext路径下面,就会出现这种情况)。

请注意,俺故意没关注被载入资源的名称。Java XML API 成为java 核心api的经历告诉,根据资源名称过滤是很不cool的idea。而且 我也没有去确认到底哪个ClassLoader被取得了,因为只要清楚原理,这很容易被推理出来。

软件介绍 本工具是对java class文件进行加密保护防止反编译的工具!本工具全面支持linux/unix/windows操作系统。 继推出v1.0版本后,获得了用户大量的支持与的反馈,我们再次推出本v2.0版,对加密算法进行了更大的改进,安全性大大提升! 众所周知,java编译后的class文件是一种中间字节字文件, 很容易被反编译工具反编译,而传统的java源代码保护方法基本都是采用混淆的方式, 但这样会带来很多麻烦,而且也不能真正保护class文件, 本工具是对class文件进行加密,采用jni的方式解密运行, 加密算法达到256位,加密后的class文件不可能被破解,反编译工具也对加密后的class文件无能为力。 运行方式: 运行时,要能正确的加载加密后的class文件, 必须使用我们提供的动态链接库classloader.dll(windows操作系统)或者libclassloader.so(Linux、Unix操作系统)。 执行java时带上参数-agentlib:\classloader 注意此处不要后缀名.dll(或者.so)。 如: 我把classloader.dll放在C:\目录下; 运行加密后的class文件命令如下: windows下执行java: java -agentlib:C:\classloader Sample Linux、Unix等系列操作系统下执行java: java -agentlib:/home/classloader Sample 或者把libclassloader.so拷贝到如“/home/yzj/jdk1.6.0_23/jre/lib/i386/”这jdk的运行目录下, 然后执行java如:java -agentlib:classloader Sample 当然如果class文件没加密,这样运行也不会出错! 应用场合: 独立的应用程序,运行java时,带上参数-agentlib:\classloader Tomcat、Jboss等Java application server修改启动脚本, 把执行java的命令行后面加上参数-agentlib:\classloader 适应环境: 操作系统:所有操作系统,Windows系统、Linux/Unix,只是运行时, 使用的动态链接库文件不一样而已,其它配置完全一样。 jdk必须1.5以上。 备注:如果下载站下载下来的程序有运行bug,请从上面两个下载地址更新软件。并给我们留言!谢谢... 升级提示:v2.1相比v2.0修改了一个注册bug。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值