详解JAVA ClassLoader

金歌  金牌会员 | 2024-11-21 14:20:29 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

概要说明

注:本篇文章是网上多篇文章的缝合,把我以为写的不错的,对我明白ClassLoader有帮助的内容写到博客中,同时我尽量让一些学Java安全的同学更详细的学习ClassLoader。
参考文章(1):https://www.cnblogs.com/luckforefforts/p/13642685.html - 简短,可以快速了解ClassLoader概念和双亲委派等机制
参考文章(2):https://cloud.tencent.com/developer/article/1383145 - 分析&调试ClassLoader部分代码,更详细
什么是ClassLoader?

ClassLoader,翻译成中文就是“类加载器”。对于平凡的 Java 开发者来说,大概接触得比较少,但对于框架开发者或者必要深度定制 Java 应用的开发者来说,这是一个非常重要的工具。明白 ClassLoader 的工作机制,能帮助我们更好地明白 Java 程序的运行原理,还能指导我们编写出更高效的代码。
ClassLoader 的主要功能是将 .class 文件加载到 JVM(Java 假造机)中,使程序能够正常运行。不过,JVM 在启动时并不会一次性将全部的类文件加载到内存中,而是根据必要动态加载。想想看,假如启动时就把全部的 JAR 包和类文件都加载到内存里,包中那么多类,内存都扛不住啦!动态加载的机制不但节省了资源,还提升了程序的启动速度和运行效率。
本文将通过学习 ClassLoader 的加载机制,深入探讨其原理和使用场景,帮助各人在实际开发中更好地明白和运用这一核心概念。
Class文件的认识

在Java中,程序运行的核心是JVM(Java假造机)。我们平时在文本编辑器或者IDE中编写的程序通常是以.java文件的形式保存的,这是最基础的源码格式。但是,这种文件本身是无法直接运行的。举个例子,我们来写一个简朴的程序:
  1. public class HelloWorld {
  2.     public static void main(String[] args) {
  3.         System.out.println("Hello world!");
  4.     }
  5. }
复制代码
写完这段代码后,必要通过命令行编译这个.java文件:
  1. javac HelloWorld.java
复制代码
编译完成后,会在同一目录下生成一个 .class 文件。这就是字节码文件,它是Java程序运行的关键。接下来,我们可以通过以下命令运行程序:
  1. java HelloWorld
复制代码
上面的例子是全部学习Java入门时都会接触到的基础内容。这里再次提到,是希望各人重新聚焦在.class文件上。因为Java假造机(JVM)并不能直接识别我们写的.java源文件,而是必要通过javac命令将它们转换成.class文件后才能实行。
更有趣的是,JVM并不限定这些.class文件的来源。只要格式符合字节码规范,无论是用C语言、Python,还是其他语言编写并正确转换为.class文件,Java假造机都可以识别并运行。这也是Java的一大优势——跨语言的灵活性。
了解了.class文件的作用后,我们再来思考一个标题:我们编写完的Java程序,是如何从代码变成运行中的程序的?换句话说,我们自己编写的各种类究竟是如何被加载到JVM中的?接下来,我们就从这个标题入手,深入探索类加载的过程。
深入探索类加载过程

学Java的时间,有没有被配置环境变量难住过?相信很多人都有这样的经历:第一次下载并安装JDK后,按照书本或者网上的教程战战兢兢地配置环境变量。配置好之后,过段时间再必要操作时,又忘了!而且是必忘。每次都要重新查资料,甚至还会感到生气:为什么这么重要的工具竟然必要手动配置环境变量?一点都不人性化!这也导致了不少初学者在这一步就打了退堂鼓,挫败感满满。
为了让这部分知识变得更直观易懂,下面我只讲解在 Windows 平台上的环境变量配置(其他平台类似),主要包罗以下三个变量:JAVA_HOME、PATH 和 CLASSPATH。
JAVA_HOME
  1. JAVA_HOME=C:\Program Files\Java\jdk-1.8
复制代码
这个变量指向的是你安装 JDK 的位置。设置它的目的是为了让系统知道 JDK 的安装目录在哪里,方便其他工具(比如 IDE)找到它。
PATH

这个变量的作用是让你在命令行中可以直接使用某些命令,而不必要输入它们的完整路径。比如,运行 javac 和 java 命令时,假如没有将 JDK 中的 bin 目录添加到 PATH 中,就必要输入全路径,非常麻烦。
  1. PATH=%JAVA_HOME%\bin\
复制代码
正确的配置方法
在原有的 PATH 路径上,添加 JDK 安装目录下的 bin 目录和 JRE 的 bin 目录。配置完成后,你可以直接在命令行中运行 javac 和 java。
CLASSPATH
  1. CLASSPATH=.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
复制代码
顾名思义,这个变量是用来指向 .jar 包或 .class 文件所在路径的。JVM 在运行时会根据 CLASSPATH 找到你必要加载的类文件或依赖包。
必要注意的一点:CLASSPATH 的值通常以 .; 开头,其中 . 表示当前目录。假如省略了这部分,JVM 大概无法识别当前目录下的类文件。
Java中的三个系统类加载器

在Java中,系统自带了三个主要的类加载器,它们各自尊责加载不同路径下的类和资源。下面我们逐一了解它们的功能和加载路径。
Bootstrap ClassLoader(引导类加载器)

这是Java中最顶层的类加载器,负责加载核心类库。它会加载 %JRE_HOME%\lib 目录下的重要文件,例如 rt.jar、resources.jar、charsets.jar 等基础类库。
注意事项

  • 引导类加载器的加载路径可以通过启动 JVM 时的 -Xbootclasspath 参数进行调整。例如:
    1. java -Xbootclasspath/a:path
    2. // 这样会将指定的路径文件追加到默认的引导加载路径中。
    复制代码
你可以在文件资源管理器中打开 %JRE_HOME%\lib 目录,查抄这些 .jar 文件是否存在。
Extension ClassLoader(扩展类加载器)

扩展类加载器用于加载 %JRE_HOME%\lib\ext 目录下的 .jar 包和 .class 文件。除此之外,还可以通过 JVM 参数 -Djava.ext.dirs=路径 指定其他扩展加载目录。
Application ClassLoader(应用类加载器)

也被称为 System ClassLoader,它是用户最常接触的类加载器,主要负责加载当前应用程序的类路径(classpath)中的全部类。用户自己编写的类和依赖库大多由它加载。
加载顺序


  • Bootstrap CLassloder
  • Extention ClassLoader
  • AppClassLoader
为了更好的明白,我们可以查看源码。  看sun.misc.Launcher,它是一个java假造机的入口应用。
  1. public class Launcher {
  2.     private static Launcher launcher = new Launcher();
  3.     private static String bootClassPath =
  4.         System.getProperty("sun.boot.class.path");
  5.     public static Launcher getLauncher() {
  6.         return launcher;
  7.     }
  8.     private ClassLoader loader;
  9.     public Launcher() {
  10.         // Create the extension class loader
  11.         ClassLoader extcl;
  12.         try {
  13.             extcl = ExtClassLoader.getExtClassLoader();
  14.         } catch (IOException e) {
  15.             throw new InternalError(
  16.                 "Could not create extension class loader", e);
  17.         }
  18.         // Now create the class loader to use to launch the application
  19.         try {
  20.             loader = AppClassLoader.getAppClassLoader(extcl);
  21.         } catch (IOException e) {
  22.             throw new InternalError(
  23.                 "Could not create application class loader", e);
  24.         }
  25.         //设置AppClassLoader为线程上下文类加载器,这个文章后面部分讲解
  26.         Thread.currentThread().setContextClassLoader(loader);
  27.     }
  28.     /*
  29.      * Returns the class loader used to launch the main application.
  30.      */
  31.     public ClassLoader getClassLoader() {
  32.         return loader;
  33.     }
  34.     /*
  35.      * The class loader used for loading installed extensions.
  36.      */
  37.     static class ExtClassLoader extends URLClassLoader {}
  38. /**
  39.      * The class loader used for loading from java.class.path.
  40.      * runs in a restricted security context.
  41.      */
  42.     static class AppClassLoader extends URLClassLoader {}
  43. }
复制代码
我们可以得到相关的信息。

  • Launcher初始化了ExtClassLoader和AppClassLoader。
  • Launcher中并没有瞥见BootstrapClassLoader,但通过System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。
先代码测试一下sun.boot.class.path是什么内容。
  1. System.out.println(System.getProperty("sun.boot.class.path"));
复制代码
效果
  1. C:\Program Files\Java\jdk-1.8\jre\lib\resources.jar;
  2. C:\Program Files\Java\jdk-1.8\jre\lib\rt.jar;
  3. C:\Program Files\Java\jdk-1.8\jre\lib\jsse.jar;
  4. C:\Program Files\Java\jdk-1.8\jre\lib\jce.jar;
  5. C:\Program Files\Java\jdk-1.8\jre\lib\charsets.jar;
  6. C:\Program Files\Java\jdk-1.8\jre\lib\jfr.jar;
  7. C:\Program Files\Java\jdk-1.8\jre\classes
复制代码
可以看到,这些全是JRE目录下的jar包或者是class文件。
ExtClassLoader工作流程:
  1. /*
  2.      * The class loader used for loading installed extensions.
  3.      */
  4.     static class ExtClassLoader extends URLClassLoader {
  5.         static {
  6.             ClassLoader.registerAsParallelCapable();
  7.         }
  8.         /**
  9.          * create an ExtClassLoader. The ExtClassLoader is created
  10.          * within a context that limits which files it can read
  11.          */
  12.         public static ExtClassLoader getExtClassLoader() throws IOException
  13.         {
  14.             final File[] dirs = getExtDirs();
  15.             try {
  16.                 // Prior implementations of this doPrivileged() block supplied
  17.                 // aa synthesized ACC via a call to the private method
  18.                 // ExtClassLoader.getContext().
  19.                 return AccessController.doPrivileged(
  20.                     new PrivilegedExceptionAction<ExtClassLoader>() {
  21.                         public ExtClassLoader run() throws IOException {
  22.                             int len = dirs.length;
  23.                             for (int i = 0; i < len; i++) {
  24.                                 MetaIndex.registerDirectory(dirs[i]);
  25.                             }
  26.                             return new ExtClassLoader(dirs);
  27.                         }
  28.                     });
  29.             } catch (java.security.PrivilegedActionException e) {
  30.                 throw (IOException) e.getException();
  31.             }
  32.         }
  33.         private static File[] getExtDirs() {
  34.             String s = System.getProperty("java.ext.dirs");
  35.             File[] dirs;
  36.             if (s != null) {
  37.                 StringTokenizer st =
  38.                     new StringTokenizer(s, File.pathSeparator);
  39.                 int count = st.countTokens();
  40.                 dirs = new File[count];
  41.                 for (int i = 0; i < count; i++) {
  42.                     dirs[i] = new File(st.nextToken());
  43.                 }
  44.             } else {
  45.                 dirs = new File[0];
  46.             }
  47.             return dirs;
  48.         }
  49. ......
  50.     }
复制代码
先前的内容有说过,可以指定-D java.ext.dirs参数来添加和改变ExtClassLoader的加载路径。这里我们通过可以编写测试代码。
  1. System.out.println(System.getProperty("java.ext.dirs"));
复制代码
效果
  1. C:\Program Files\Java\jdk-1.8\jre\lib\ext;
  2. C:\Windows\Sun\Java\lib\ext
复制代码
AppClassLoader工作流程
  1. /**
  2. * The class loader used for loading from java.class.path.
  3. * runs in a restricted security context.
  4. */
  5. static class AppClassLoader extends URLClassLoader {
  6.     public static ClassLoader getAppClassLoader(final ClassLoader extcl)
  7.         throws IOException
  8.     {
  9.         final String s = System.getProperty("java.class.path");
  10.         final File[] path = (s == null) ? new File[0] : getClassPath(s);
  11.         return AccessController.doPrivileged(
  12.             new PrivilegedAction<AppClassLoader>() {
  13.                 public AppClassLoader run() {
  14.                 URL[] urls =
  15.                     (s == null) ? new URL[0] : pathToURLs(path);
  16.                 return new AppClassLoader(urls, extcl);
  17.             }
  18.         });
  19.     }
  20.     ......
  21. }
复制代码
可以看到AppClassLoader加载的就是java.class.path下的路径。我们同样打印它的值。
  1. System.out.println(System.getProperty("java.class.path"));
复制代码
效果
  1. C:\Program Files\Java\jdk-1.8\jre\lib\charsets.jar;
  2. C:\Program Files\Java\jdk-1.8\jre\lib\deploy.jar;
  3. ......
  4. C:\Users\15137\Desktop\Workspace\vuln-learn\target\classes;
  5. C:\Users\15137\.m2\repository\javax\enterprise\cdi-api\2.0.SP1\cdi-api-2.0.SP1.jar;
  6. ......
  7. C:\Users\15137\.m2\repository\javassist\javassist\3.12.1.GA\javassist-3.12.1.GA.jar;
  8. C:\Users\15137\.m2\repository\commons-beanutils\commons-beanutils\1.9.1\commons-beanutils-1.9.1.jar;
复制代码
包罗了全部的依赖路径(.jar 文件或目录),它是 类路径(classpath) 的配置内容。包罗了当前项目中的全部的依赖jar包。
自此我们已经知道了BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.path、java.ext.dirs和java.class.path来加载资源文件的。
来写个demo代码做个实验。
  1. public class ClassLoaderTest {
  2.     public static void main(String[] args) {
  3.         // TODO Auto-generated method stub
  4.         ClassLoader cl = Test.class.getClassLoader();
  5.         System.out.println("ClassLoader is:"+cl.toString());
  6.     }
  7. }
  8. class Test{}
复制代码
效果
  1. ClassLoader is:sun.misc.Launcher$AppClassLoader@22d8cfe0
复制代码
也就是说明Test.class文件是由AppClassLoader加载的。
这个Test类是我们自己编写的,那么int.class或者是String.class的加载是由谁完成的呢?  我们可以在代码中尝试
  1. public class ClassLoaderTest {
  2.     public static void main(String[] args) {
  3.         // TODO Auto-generated method stub
  4.         ClassLoader cl = Test.class.getClassLoader();
  5.         System.out.println("ClassLoader is:"+cl.toString());
  6.         cl = int.class.getClassLoader();
  7.         System.out.println("ClassLoader is:"+cl.toString());
  8.     }
  9. }
复制代码
运行一下,报错了
  1. ClassLoader is:sun.misc.Launcher$AppClassLoader@22d8cfe0
  2. Exception in thread "main" java.lang.NullPointerException
  3.         at ClassLoaderLearn.main(ClassLoaderLearn.java:6)
复制代码
提示的是空指针,意思是int.class这类基础类没有类加载器加载?
当然不是!  int.class是由Bootstrap ClassLoader加载的。要想弄明白这些,我们起首得知道一个前提。
每个类加载器都有一个父加载器

每个类加载器都有一个父加载器,比如加载Test.class是由AppClassLoader完成,那么AppClassLoader也有一个父加载器,怎么样获取呢?很简朴,通过getParent方法。比如代码可以这样编写:
  1. ClassLoader cl = Test.class.getClassLoader();
  2. System.out.println("ClassLoader is:"+cl.toString());
  3. System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
复制代码
运行效果如下
  1. ClassLoader is:sun.misc.Launcher$AppClassLoader@22d8cfe0
  2. ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@73a28541
复制代码
这个说明,AppClassLoader的父加载器是ExtClassLoader。那么ExtClassLoader的父加载器又是谁呢?
  1. ClassLoader cl = Test.class.getClassLoader();
  2. System.out.println("ClassLoader is:"+cl.toString());
  3. System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
复制代码
效果
  1. ClassLoader is:sun.misc.Launcher$AppClassLoader@22d8cfe0
  2. ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@73a28541Exception in thread "main" java.lang.NullPointerException        at ClassLoaderLearn.main(ClassLoaderLearn.java:7)
复制代码
又是一个空指针非常,这表明ExtClassLoader也没有父加载器。那么,为什么标题又是每一个加载器都有一个父加载器呢?这不矛盾吗?为了解释这一点,我们还必要看下面的一个基础前提。
父加载器不是父类

再看一下ExtClassLoader和AppClassLoader的定义。
  1. static class ExtClassLoader extends URLClassLoader {}
  2. static class AppClassLoader extends URLClassLoader {}
复制代码
可以瞥见ExtClassLoader和AppClassLoader同样继承自URLClassLoader,但上面一小节代码中,为什么调用AppClassLoader的getParent()代码会得到ExtClassLoader的实例呢?先从URLClassLoader提及,这个类又是什么?
先看一个ClassLoader的类继承关系图

URLClassLoader的源码中并没有找到getParent()方法。这个方法在ClassLoader.java中。
  1. public abstract class ClassLoader {
  2.     // The parent class loader for delegation
  3.     // Note: VM hardcoded the offset of this field, thus all new fields
  4.     // must be added *after* it.
  5.     private final ClassLoader parent;
  6.     // The class loader for the system
  7.         // @GuardedBy("ClassLoader.class")
  8.     private static ClassLoader scl;
  9.     private ClassLoader(Void unused, ClassLoader parent) {
  10.         this.parent = parent;
  11.         ...
  12.     }
  13.     protected ClassLoader(ClassLoader parent) {
  14.         this(checkCreateClassLoader(), parent);
  15.     }
  16.     protected ClassLoader() {
  17.         this(checkCreateClassLoader(), getSystemClassLoader());
  18.     }
  19.     public final ClassLoader getParent() {
  20.         if (parent == null)
  21.             return null;
  22.         return parent;
  23.     }
  24.     public static ClassLoader getSystemClassLoader() {
  25.         initSystemClassLoader();
  26.         if (scl == null) {
  27.             return null;
  28.         }
  29.         return scl;
  30.     }
  31.     private static synchronized void initSystemClassLoader() {
  32.         if (!sclSet) {
  33.             if (scl != null)
  34.                 throw new IllegalStateException("recursive invocation");
  35.             sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
  36.             if (l != null) {
  37.                 Throwable oops = null;
  38.                 //通过Launcher获取ClassLoader
  39.                 scl = l.getClassLoader();
  40.                 try {
  41.                     scl = AccessController.doPrivileged(
  42.                         new SystemClassLoaderAction(scl));
  43.                 } catch (PrivilegedActionException pae) {
  44.                     oops = pae.getCause();
  45.                     if (oops instanceof InvocationTargetException) {
  46.                         oops = oops.getCause();
  47.                     }
  48.                 }
  49.                 if (oops != null) {
  50.                     if (oops instanceof Error) {
  51.                         throw (Error) oops;
  52.                     } else {
  53.                         // wrap the exception
  54.                         throw new Error(oops);
  55.                     }
  56.                 }
  57.             }
  58.             sclSet = true;
  59.         }
  60.     }
  61. }
复制代码
可以看到getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:  1. 由外部类创建ClassLoader时直接指定一个ClassLoader为parent。  2.  由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时假如没有指定parent,那么它的parent默认就是AppClassLoader。
我们主要研究的是ExtClassLoader与AppClassLoader的parent的来源,恰好它们与Launcher类有关,我们上面已经粘贴过Launcher的部分代码。
  1. public class Launcher {
  2.     private static URLStreamHandlerFactory factory = new Factory();
  3.     private static Launcher launcher = new Launcher();
  4.     private static String bootClassPath =
  5.         System.getProperty("sun.boot.class.path");
  6.     public static Launcher getLauncher() {
  7.         return launcher;
  8.     }
  9.     private ClassLoader loader;
  10.     public Launcher() {
  11.         // Create the extension class loader
  12.         ClassLoader extcl;
  13.         try {
  14.             extcl = ExtClassLoader.getExtClassLoader();
  15.         } catch (IOException e) {
  16.             throw new InternalError(
  17.                 "Could not create extension class loader", e);
  18.         }
  19.         // Now create the class loader to use to launch the application
  20.         try {
  21.         //将ExtClassLoader对象实例传递进去
  22.             loader = AppClassLoader.getAppClassLoader(extcl);
  23.         } catch (IOException e) {
  24.             throw new InternalError(
  25.                 "Could not create application class loader", e);
  26.         }
  27.     }
  28.         public ClassLoader getClassLoader() {
  29.         return loader;
  30.     }
  31.         static class ExtClassLoader extends URLClassLoader {
  32.         /**
  33.          * create an ExtClassLoader. The ExtClassLoader is created
  34.          * within a context that limits which files it can read
  35.          */
  36.         public static ExtClassLoader getExtClassLoader() throws IOException
  37.         {
  38.             final File[] dirs = getExtDirs();
  39.             try {
  40.                 // Prior implementations of this doPrivileged() block supplied
  41.                 // aa synthesized ACC via a call to the private method
  42.                 // ExtClassLoader.getContext().
  43.                 return AccessController.doPrivileged(
  44.                     new PrivilegedExceptionAction<ExtClassLoader>() {
  45.                         public ExtClassLoader run() throws IOException {
  46.                             //ExtClassLoader在这里创建
  47.                             return new ExtClassLoader(dirs);
  48.                         }
  49.                     });
  50.             } catch (java.security.PrivilegedActionException e) {
  51.                 throw (IOException) e.getException();
  52.             }
  53.         }
  54.         /*
  55.          * Creates a new ExtClassLoader for the specified directories.
  56.          */
  57.         public ExtClassLoader(File[] dirs) throws IOException {
  58.             super(getExtURLs(dirs), null, factory);
  59.         }
  60.     }
  61. }
复制代码
我们必要注意
  1. // 重点是这一段代码
  2. ClassLoader extcl;
  3. extcl = ExtClassLoader.getExtClassLoader();
  4. loader = AppClassLoader.getAppClassLoader(extcl);
复制代码
代码已经说明了标题AppClassLoader的parent是一个ExtClassLoader实例。
ExtClassLoader并没有直接找到对parent的赋值。它调用了它的父类也就是URLClassLoder的构造方法并通报了3个参数。
  1. public ExtClassLoader(File[] dirs) throws IOException {
  2.             super(getExtURLs(dirs), null, factory);   
  3. }
  4. // 对应的代码
  5. public  URLClassLoader(URL[] urls, ClassLoader parent,
  6.                           URLStreamHandlerFactory factory) {
  7.      super(parent);
  8. }
复制代码
答案已经很明了了,ExtClassLoader的parent为null。
上面张贴这么多代码也是为了说明AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。这符合我们之前编写的测试代码。
不过,细心的同学发现,还是有疑问的我们只看到ExtClassLoader和AppClassLoader的创建,那么BootstrapClassLoader呢?
另有,ExtClassLoader的父加载器为null,但是Bootstrap CLassLoader却可以当成它的父加载器这又是为何呢?
我们继承往下进行。
Bootstrap ClassLoader

从Java假造机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot假造机中),是假造机自身的一部分;另一种就是全部其他的类加载器,这些类加载器都有Java语言实现,独立于假造机外部,而且全部继承自java.lang.ClassLoader。
Bootstrap ClassLoader是由C/C++编写的,它本身是假造机的一部分,以是它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后呢,我们前面已经分析了,JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的征象。
双亲委托

我们终于来到了这一步了。  一个类加载器查找class和resource时,是通过“委托模式”进行的,它起首判断这个class是不是已经加载乐成,假如没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,假如Bootstrap classloader找到了,直接返回,假如没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。

这个机制分为两部分,第一部分是向上委托的过程:

  • 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
  • 递归,重复第1步调的操作。
  • 假如ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它起首查找缓存。假如有的话则返回,没有的话,则开始第二部分过程。
假如上诉还是没有找到对应的类,则开始第二部分的过程:

  • 假如Bootstrap ClassLoader缓存没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
  • Bootstrap ClassLoader假如没有查找乐成,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找乐成绩返回,查找不乐成,再向下让子加载器找。
  • ExtClassLoader查找不乐成,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。假如没有找到就让子类找,假如没有子类会怎么样?抛出各种非常。
上面已经详细先容了加载过程,但详细为什么是这样加载,我们还必要了解几个个重要的方法loadClass()、findLoadedClass()、findClass()、defineClass()。
重要方法

loadClass

JDK文档中是这样写的,通过指定的全限定类名加载class,它通过同名的loadClass(String,boolean)方法。
  1. protected Class<?> loadClass(String name,
  2.                              boolean resolve)
  3.                       throws ClassNotFoundException
复制代码
上面是方法原型,一般实现这个方法的步调是

  • 实行findLoadedClass(String)去检测这个class是不是已经加载过了。
  • 实行父加载器的loadClass方法。假如父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍旧说Bootstrap ClassLoader是它的父加载器。
  • 假如向上委托父加载器没有加载乐成,则通过findClass(String)查找。
假如class在上面的步调中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。 我们可以从源代码看出这个步调。
  1. protected Class<?> loadClass(String name, boolean resolve)
  2.         throws ClassNotFoundException
  3.     {
  4.         synchronized (getClassLoadingLock(name)) {
  5.             // 首先,检测是否已经加载
  6.             Class<?> c = findLoadedClass(name);
  7.             if (c == null) {
  8.                 long t0 = System.nanoTime();
  9.                 try {
  10.                     if (parent != null) {
  11.                         //父加载器不为空则调用父加载器的loadClass
  12.                         c = parent.loadClass(name, false);
  13.                     } else {
  14.                         //父加载器为空则调用Bootstrap Classloader
  15.                         c = findBootstrapClassOrNull(name);
  16.                     }
  17.                 } catch (ClassNotFoundException e) {
  18.                     // ClassNotFoundException thrown if class not found
  19.                     // from the non-null parent class loader
  20.                 }
  21.                 if (c == null) {
  22.                     // If still not found, then invoke findClass in order
  23.                     // to find the class.
  24.                     long t1 = System.nanoTime();
  25.                     //父加载器没有找到,则调用findclass
  26.                     c = findClass(name);
  27.                     // this is the defining class loader; record the stats
  28.                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  29.                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  30.                     sun.misc.PerfCounter.getFindClasses().increment();
  31.                 }
  32.             }
  33.             if (resolve) {
  34.                 //调用resolveClass()
  35.                 resolveClass(c);
  36.             }
  37.             return c;
  38.         }
  39.     }
复制代码
代码解释了双亲委托。
另外,要注意的是假如要编写一个classLoader的子类,也就是自定义一个classloader,建议覆盖findClass()方法,而不要直接改写loadClass()方法。
另外
  1. if (parent != null) {
  2.     //父加载器不为空则调用父加载器的loadClass
  3.     c = parent.loadClass(name, false);
  4. } else {
  5.     //父加载器为空则调用Bootstrap Classloader
  6.     c = findBootstrapClassOrNull(name);
  7. }
复制代码
前面说过ExtClassLoader的parent为null,以是它向上委托时,系统会为它指定Bootstrap ClassLoader。
自定义ClassLoader

不知道各人有没有发现,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。假如在某种情况下,我们必要动态加载一些东西呢?比如从D盘某个文件夹加载一个class文件,或者从网络上下载class主内容然后再进行加载,这样可以吗?
假如要这样做的话,必要我们自定义一个classloader。
自定义步调


  • 编写一个类继承自ClassLoader抽象类。
  • 复写它的findClass()方法。
  • 在findClass()方法中调用defineClass()。
defineClass

这个方法在编写自定义classloader的时间非常重要,它能将class二进制内容转换成Class对象,假如不符合要求的会抛出各种非常。
注意点

一个ClassLoader创建时假如没有指定parent,那么它的parent默认就是AppClassLoader。
上面说的是,假如自定义一个ClassLoader,默认的parent父加载器是AppClassLoader,因为这样就能够包管它能访问系统内置加载器加载乐成的class文件。
自定义ClassLoader示例

假设我们必要一个自定义的classloader,默认加载路径为D:\lib下的jar包和资源。
我们写编写一个测试用的类文件,Test.java
  1. package com.frank.test;
  2. public class Test {
  3.     public void say(){
  4.         System.out.println("Say Hello");
  5.     }
  6. }
复制代码
然后将它编译过年class文件Test.class放到D:\lib这个路径下。
DiskClassLoader

我们编写DiskClassLoader的代码。
  1. import java.io.ByteArrayOutputStream;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.IOException;
  6. public class DiskClassLoader extends ClassLoader {
  7.     private String mLibPath;
  8.     public DiskClassLoader(String path) {
  9.         // TODO Auto-generated constructor stub
  10.         mLibPath = path;
  11.     }
  12.     @Override
  13.     protected Class<?> findClass(String name) throws ClassNotFoundException {
  14.         // TODO Auto-generated method stub
  15.         String fileName = getFileName(name);
  16.         File file = new File(mLibPath,fileName);
  17.         try {
  18.             FileInputStream is = new FileInputStream(file);
  19.             ByteArrayOutputStream bos = new ByteArrayOutputStream();
  20.             int len = 0;
  21.             try {
  22.                 while ((len = is.read()) != -1) {
  23.                     bos.write(len);
  24.                 }
  25.             } catch (IOException e) {
  26.                 e.printStackTrace();
  27.             }
  28.             byte[] data = bos.toByteArray();
  29.             is.close();
  30.             bos.close();
  31.             return defineClass(name,data,0,data.length);
  32.         } catch (IOException e) {
  33.             // TODO Auto-generated catch block
  34.             e.printStackTrace();
  35.         }
  36.         return super.findClass(name);
  37.     }
  38.     //获取要加载 的class文件名
  39.     private String getFileName(String name) {
  40.         // TODO Auto-generated method stub
  41.         int index = name.lastIndexOf('.');
  42.         if(index == -1){
  43.             return name+".class";
  44.         }else{
  45.             return name.substring(index+1)+".class";
  46.         }
  47.     }
  48. }
复制代码
我们在findClass()方法中定义了查找class的方法,然后数据通过defineClass()生成了Class对象。
测试

如今我们要编写测试代码。我们知道假如调用一个Test对象的say方法,它会输出”Say Hello”这条字符串。但如今是我们把Test.class放置在应用工程全部的目录之外,我们必要加载它,然后实行它的方法。详细效果如何呢?我们编写的DiskClassLoader能不能顺利完成任务呢?我们拭目以待。
  1. import java.lang.reflect.InvocationTargetException;
  2. import java.lang.reflect.Method;
  3. public class ClassLoaderTest {
  4.     public static void main(String[] args) {
  5.         // TODO Auto-generated method stub
  6.         //创建自定义classloader对象。
  7.         DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
  8.         try {
  9.             //加载class文件
  10.             Class c = diskLoader.loadClass("com.frank.test.Test");
  11.             if(c != null){
  12.                 try {
  13.                     Object obj = c.newInstance();
  14.                     Method method = c.getDeclaredMethod("say",null);
  15.                     //通过反射调用Test类的say方法
  16.                     method.invoke(obj, null);
  17.                 } catch (InstantiationException | IllegalAccessException
  18.                         | NoSuchMethodException
  19.                         | SecurityException |
  20.                         IllegalArgumentException |
  21.                         InvocationTargetException e) {
  22.                     // TODO Auto-generated catch block
  23.                     e.printStackTrace();
  24.                 }
  25.             }
  26.         } catch (ClassNotFoundException e) {
  27.             // TODO Auto-generated catch block
  28.             e.printStackTrace();
  29.         }
  30.     }
  31. }
复制代码
实行效果:
  1. Say Hello
复制代码
可以看到,Test类的say方法正确实行,也就是我们写的DiskClassLoader编写乐成。
总结

关键字 - 路径


  • 从开篇的环境变量
  • 到3个主要的JDK自带的类加载器
  • 到自定义的ClassLoader
它们的关联部分就是路径,也就是要加载的class或者是资源的路径。  BootStrap ClassLoader、ExtClassLoader、AppClassLoader都是加载指定路径下的jar包。假如我们要突破这种限定,实现自己某些特殊的需求,我们就得自定义ClassLoader,自已指定加载的路径,可以是磁盘、内存、网络或者其它。
以是,你说路径能不能成为它们的关键字?
当然上面的只是我个人的见解,大概不正确,但现阶段,这样有利于自己的学习明白。
注意:在JDK1.2之前,类加载尚未引入双亲委派模式,因此实现自定义类加载器时常常重写loadClass方法,提供双亲委派逻辑,从JDK1.2之后,双亲委派模式已经被引入到类加载体系中,自定义类加载器时不必要在自己写双亲委派的逻辑,因此不鼓励重写loadClass方法,而推荐重写findClass方法。
在Java中,任意一个类都必要由加载它的类加载器和这个类本身一同确定其在java假造机中的唯一性,即比较两个类是否相称,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类肯定不相称(这里的相称包罗代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的效果)。
类加载器双亲委派模型是从JDK1.2以后引入的,而且只是一种推荐的模型,不是强制要求的,因此有一些没有遵照双亲委派模型的特例:(了解)
(1).在JDK1.2之前,自定义类加载器都要覆盖loadClass方法去实现加载类的功能,JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类加载器进行类加载,只有父类加载器无法完成类加载哀求时才调用自己的findClass方法进行类加载,因此在JDK1.2之前的类加载的loadClass方法没有遵照双亲委派模型,因此在JDK1.2之后,自定义类加载器不推荐覆盖loadClass方法,而只必要覆盖findClass方法即可。
(2).双亲委派模式很好地办理了各个类加载器的基础类同一标题,越基础的类由越上层的类加载器进行加载,但是这个基础类同一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类加载器进行类加载。为了办理这个标题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器。
JavaEE只是一个规范,sun公司只给出了接口规范,详细的实现由各个厂商进行实现,因此JNDI,JDBC,JAXB等这些第三方的实现库就可以被JDK的类库所调用。线程上下文类加载器也没有遵照双亲委派模型。
(3).近年来的热码替换,模块热部署等应用要求不用重启java假造机就可以实现代码模块的即插即用,催生了OSGi技术,在OSGi中类加载器体系被发展为网状结构。OSGi也没有完全遵照双亲委派模型。
其它

下边是原有博客中对ClassLoader的其他扩展的分析和Demo代码编写,对于大多数同学来说,到这个地方就对ClassLoader有了一定了解,这实在也就够了。假如想再深入学习的话也可以看一下原文章:https://cloud.tencent.com/developer/article/1383145
自定义ClassLoader还能做什么

突破了JDK系统内置加载路径的限定之后,我们就可以编写自定义ClassLoader,然后剩下的就叫给开发者你自己了。你可以按照自己的意愿进行业务的定制,将ClassLoader玩出格式来。
玩出花之Class解密类加载器

常见的用法是将Class文件按照某种加密本领进行加密,然后按照规则编写自定义的ClassLoader进行解密,这样我们就可以在程序中加载特定了类,而且这个类只能被我们自定义的加载器进行加载,提高了程序的安全性。
下面,我们编写代码。
1.定义加密解密协议

加密息争密的协议有很多种,详细怎么定看业务必要。在这里,为了便于演示,我简朴地将加密解密定义为异或运算。当一个文件进行异或运算后,产生了加密文件,再进行一次异或后,就进行了解密。
2.编写加密工具类
  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileNotFoundException;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. public class FileUtils {
  7.     public static void test(String path){
  8.         File file = new File(path);
  9.         try {
  10.             FileInputStream fis = new FileInputStream(file);
  11.             FileOutputStream fos = new FileOutputStream(path+"en");
  12.             int b = 0;
  13.             int b1 = 0;
  14.             try {
  15.                 while((b = fis.read()) != -1){
  16.                     //每一个byte异或一个数字2
  17.                     fos.write(b ^ 2);
  18.                 }
  19.                 fos.close();
  20.                 fis.close();
  21.             } catch (IOException e) {
  22.                 // TODO Auto-generated catch block
  23.                 e.printStackTrace();
  24.             }
  25.         } catch (FileNotFoundException e) {
  26.             // TODO Auto-generated catch block
  27.             e.printStackTrace();
  28.         }
  29.     }
  30. }
复制代码
我们再写测试代码
  1. FileUtils.test("D:\\lib\\Test.class");
复制代码
然后可以瞥见路径D:\\lib\\Test.class下Test.class生成了Test.classen文件。
编写自定义classloader,DeClassLoader
  1. import java.io.ByteArrayOutputStream;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. public class DeClassLoader extends ClassLoader {
  6.     private String mLibPath;
  7.     public DeClassLoader(String path) {
  8.         // TODO Auto-generated constructor stub
  9.         mLibPath = path;
  10.     }
  11.     @Override
  12.     protected Class<?> findClass(String name) throws ClassNotFoundException {
  13.         // TODO Auto-generated method stub
  14.         String fileName = getFileName(name);
  15.         File file = new File(mLibPath,fileName);
  16.         try {
  17.             FileInputStream is = new FileInputStream(file);
  18.             ByteArrayOutputStream bos = new ByteArrayOutputStream();
  19.             int len = 0;
  20.             byte b = 0;
  21.             try {
  22.                 while ((len = is.read()) != -1) {
  23.                     //将数据异或一个数字2进行解密
  24.                     b = (byte) (len ^ 2);
  25.                     bos.write(b);
  26.                 }
  27.             } catch (IOException e) {
  28.                 e.printStackTrace();
  29.             }
  30.             byte[] data = bos.toByteArray();
  31.             is.close();
  32.             bos.close();
  33.             return defineClass(name,data,0,data.length);
  34.         } catch (IOException e) {
  35.             // TODO Auto-generated catch block
  36.             e.printStackTrace();
  37.         }
  38.         return super.findClass(name);
  39.     }
  40.     //获取要加载 的class文件名
  41.     private String getFileName(String name) {
  42.         // TODO Auto-generated method stub
  43.         int index = name.lastIndexOf('.');
  44.         if(index == -1){
  45.             return name+".classen";
  46.         }else{
  47.             return name.substring(index+1)+".classen";
  48.         }
  49.     }
  50. }
复制代码
测试

我们可以在ClassLoaderTest.java中的main方法中如下编码:
  1. DeClassLoader diskLoader = new DeClassLoader("D:\\lib");
  2. try {
  3.     //加载class文件
  4.     Class c = diskLoader.loadClass("com.frank.test.Test");
  5.     if(c != null){
  6.         try {
  7.             Object obj = c.newInstance();
  8.             Method method = c.getDeclaredMethod("say",null);
  9.             //通过反射调用Test类的say方法
  10.             method.invoke(obj, null);
  11.         } catch (InstantiationException | IllegalAccessException
  12.                 | NoSuchMethodException
  13.                 | SecurityException |
  14.                 IllegalArgumentException |
  15.                 InvocationTargetException e) {
  16.             // TODO Auto-generated catch block
  17.             e.printStackTrace();
  18.         }
  19.     }
  20. } catch (ClassNotFoundException e) {
  21.     // TODO Auto-generated catch block
  22.     e.printStackTrace();
  23. }
复制代码
查看运行效果是:
  1. Say Hello
复制代码
可以看到了,同样乐成了。如今,我们有两个自定义的ClassLoaderiskClassLoader和DeClassLoader,我们可以尝试一下,看看DiskClassLoader能不能加载Test.classen文件也就是Test.class加密后的文件。
我们起首移除D:\\lib\\Test.class文件,只剩下一下Test.classen文件,然后进行代码的测试。
  1. DeClassLoader diskLoader1 = new DeClassLoader("D:\\lib");
  2.     try {
  3.         //加载class文件
  4.         Class c = diskLoader1.loadClass("com.frank.test.Test");
  5.         if(c != null){
  6.             try {
  7.                 Object obj = c.newInstance();
  8.                 Method method = c.getDeclaredMethod("say",null);
  9.                 //通过反射调用Test类的say方法
  10.                 method.invoke(obj, null);
  11.             } catch (InstantiationException | IllegalAccessException
  12.                     | NoSuchMethodException
  13.                     | SecurityException |
  14.                     IllegalArgumentException |
  15.                     InvocationTargetException e) {
  16.                 // TODO Auto-generated catch block
  17.                 e.printStackTrace();
  18.             }
  19.         }
  20.     } catch (ClassNotFoundException e) {
  21.         // TODO Auto-generated catch block
  22.         e.printStackTrace();
  23.     }
  24.     DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
  25.     try {
  26.         //加载class文件
  27.         Class c = diskLoader.loadClass("com.frank.test.Test");
  28.         if(c != null){
  29.             try {
  30.                 Object obj = c.newInstance();
  31.                 Method method = c.getDeclaredMethod("say",null);
  32.                 //通过反射调用Test类的say方法
  33.                 method.invoke(obj, null);
  34.             } catch (InstantiationException | IllegalAccessException
  35.                     | NoSuchMethodException
  36.                     | SecurityException |
  37.                     IllegalArgumentException |
  38.                     InvocationTargetException e) {
  39.                 // TODO Auto-generated catch block
  40.                 e.printStackTrace();
  41.             }
  42.         }
  43.     } catch (ClassNotFoundException e) {
  44.         // TODO Auto-generated catch block
  45.         e.printStackTrace();
  46.     }
  47. }
复制代码
我们可以看到。DeClassLoader运行正常,而DiskClassLoader却找不到Test.class的类,而且它也无法加载Test.classen文件。
Context ClassLoader 线程上下文类加载器

前面讲到过Bootstrap ClassLoader、ExtClassLoader、AppClassLoader,如今又出来这么一个类加载器,这是为什么?
前面三个之以是放在前面讲,是因为它们是真实存在的类,而且服从”双亲委托“的机制。而ContextClassLoader实在只是一个概念。
查看Thread.java源码可以发现
  1. public class Thread implements Runnable {
  2. /* The context ClassLoader for this thread */
  3.    private ClassLoader contextClassLoader;
  4.    public void setContextClassLoader(ClassLoader cl) {
  5.        SecurityManager sm = System.getSecurityManager();
  6.        if (sm != null) {
  7.            sm.checkPermission(new RuntimePermission("setContextClassLoader"));
  8.        }
  9.        contextClassLoader = cl;
  10.    }
  11.    public ClassLoader getContextClassLoader() {
  12.        if (contextClassLoader == null)
  13.            return null;
  14.        SecurityManager sm = System.getSecurityManager();
  15.        if (sm != null) {
  16.            ClassLoader.checkClassLoaderPermission(contextClassLoader,
  17.                                                   Reflection.getCallerClass());
  18.        }
  19.        return contextClassLoader;
  20.    }
  21. }
复制代码
contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。而且子线程默认使用父线程的ClassLoader除非子线程特别设置。
我们同样可以编写代码来加深明白。   如今有2个SpeakTest.class文件,一个源码是
  1. package com.frank.test;
  2. public class SpeakTest implements ISpeak {
  3.     @Override
  4.     public void speak() {
  5.         // TODO Auto-generated method stub
  6.         System.out.println("Test");
  7.     }
  8. }
复制代码
它生成的SpeakTest.class文件放置在D:\\lib\\test目录下。   另外ISpeak.java代码
  1. package com.frank.test;
  2. public interface ISpeak {
  3.     public void speak();
  4. }
复制代码
然后,我们在这里还实现了一个SpeakTest.java
  1. package com.frank.test;
  2. public class SpeakTest implements ISpeak {
  3.     @Override
  4.     public void speak() {
  5.         // TODO Auto-generated method stub
  6.         System.out.println("I\' frank");
  7.     }
  8. }
复制代码
它生成的SpeakTest.class文件放置在D:\\lib目录下。
然后我们还要编写另外一个ClassLoader,DiskClassLoader1.java这个ClassLoader的代码和DiskClassLoader.java代码一致,我们要在DiskClassLoader1中加载位置于D:\\lib\\test中的SpeakTest.class文件。
测试代码:
  1. DiskClassLoader1 diskLoader1 = new DiskClassLoader1("D:\\lib\\test");
  2. Class cls1 = null;
  3. try {
  4. //加载class文件
  5. cls1 = diskLoader1.loadClass("com.frank.test.SpeakTest");
  6. System.out.println(cls1.getClassLoader().toString());
  7. if(cls1 != null){
  8.     try {
  9.         Object obj = cls1.newInstance();
  10.         //SpeakTest1 speak = (SpeakTest1) obj;
  11.         //speak.speak();
  12.         Method method = cls1.getDeclaredMethod("speak",null);
  13.         //通过反射调用Test类的speak方法
  14.         method.invoke(obj, null);
  15.     } catch (InstantiationException | IllegalAccessException
  16.             | NoSuchMethodException
  17.             | SecurityException |
  18.             IllegalArgumentException |
  19.             InvocationTargetException e) {
  20.         // TODO Auto-generated catch block
  21.         e.printStackTrace();
  22.     }
  23. }
  24. } catch (ClassNotFoundException e) {
  25. // TODO Auto-generated catch block
  26. e.printStackTrace();
  27. }
  28. DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
  29. System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
  30. new Thread(new Runnable() {
  31.     @Override
  32.     public void run() {
  33.         System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString());
  34.         // TODO Auto-generated method stub
  35.         try {
  36.             //加载class文件
  37.         //  Thread.currentThread().setContextClassLoader(diskLoader);
  38.             //Class c = diskLoader.loadClass("com.frank.test.SpeakTest");
  39.             ClassLoader cl = Thread.currentThread().getContextClassLoader();
  40.             Class c = cl.loadClass("com.frank.test.SpeakTest");
  41.             // Class c = Class.forName("com.frank.test.SpeakTest");
  42.             System.out.println(c.getClassLoader().toString());
  43.             if(c != null){
  44.                 try {
  45.                     Object obj = c.newInstance();
  46.                     //SpeakTest1 speak = (SpeakTest1) obj;
  47.                     //speak.speak();
  48.                     Method method = c.getDeclaredMethod("speak",null);
  49.                     //通过反射调用Test类的say方法
  50.                     method.invoke(obj, null);
  51.                 } catch (InstantiationException | IllegalAccessException
  52.                         | NoSuchMethodException
  53.                         | SecurityException |
  54.                         IllegalArgumentException |
  55.                         InvocationTargetException e) {
  56.                     // TODO Auto-generated catch block
  57.                     e.printStackTrace();
  58.                 }
  59.             }
  60.         } catch (ClassNotFoundException e) {
  61.             // TODO Auto-generated catch block
  62.             e.printStackTrace();
  63.         }
  64.     }
  65. }).start();
复制代码
我们可以得到如下的信息:

  • DiskClassLoader1加载乐成了SpeakTest.class文件并实行乐成。
  • 子线程的ContextClassLoader是AppClassLoader。
  • AppClassLoader加载不了父线程当中已经加载的SpeakTest.class内容。
我们修改一下代码,在子线程开头处加上这么一句内容。
可以看到子线程的ContextClassLoader变成了DiskClassLoader。
继承改动代码:
  1. Thread.currentThread().setContextClassLoader(diskLoader);
复制代码
可以看到DiskClassLoader1和DiskClassLoader分别加载了自己路径下的SpeakTest.class文件,而且它们的类名是一样的com.frank.test.SpeakTest,但是实行效果不一样,因为它们的实际内容不一样。
Context ClassLoader的运用机遇

实在这个我也不是很清楚,我的主业是Android,研究ClassLoader也是为了更好的研究Android。网上的答案说是适应那些Web服务框架软件如Tomcat等。主要为了加载不同的APP,因为加载器不一样,同一份class文件加载后生成的类是不相称的。假如有同学想多了解更多的细节,请自行查阅相关资料。
总结


  • ClassLoader用来加载class文件的。
  • 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。
  • 可以自定义ClassLoader一般覆盖findClass()方法。
  • ContextClassLoader与线程相关,可以获取和设置,可以绕过双亲委托的机制。
下一步


  • 你可以研究ClassLoader在Web容器内的应用了,如Tomcat。
  • 可以尝试以这个为基础,继承学习Android中的ClassLoader机制。
原博客参考引用文献

我这篇文章写了好几天,修修改改,然后加上自己的明白。参考了下面的这些网站。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表