Live2D

初学类加载器

java之ClassLoader

1. 类的加载过程

我们编写的“.java”文件需要经过java编译器编译成“.class”文件,当程序运行时,如果需要用到某个

类,jvm会去加载它,并在内存中创建对应的class对象,这个过程被称为类的加载

简单来说:将class文件读入内存,并为之创建一个Class对象,

既然是jvm自动去完成的,那么为什么学习类加载器,在什么工作场景中可以用到?

插件功能,比如:

需求:解析Excel,一般用poi就可以搞定,但是目前Excel模板不确定,而且用户不希望每次增加一新

的模板都需要重启应用,这时候应该怎么办?

解决:把解析Excel代码的抽出来,打成一个jar包,上传到应用中,每次用户上传Excel的时候,选择

对应的jar包去解析,因为需要动态的加载jar包,这时候就要用到类加载器把jar包加载到内存中,通

过反射执行指定的方法来获取结果。

总之如果碰到动态加载jar包、.class文件这种场景,就应该使用类加载器

2. 类加载器

jvm通过类加载器把“.class”文件加载到内存中,java默认提供了3个类加载器,分别是:

    • Bootstrap ClassLoader 启动类加载器
    • ExtClassLoader 扩展类加载器
    • AppClassLoader 系统类加载器

三个类加载器各有不同的作用。

1. Bootstrap ClassLoader

主要加载JDK8核心类库,用C++语言实现的,加载时的搜索路径是由 sun.boot.class.path 所指定的,比如:%JRE_HOME%\jre\lib下的rt.jar、resources.jar、charsets.jar等。
其中 rt.jar 里面就存放着我们经常用的java API

JDK8和11略有不同

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {
         // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//AppClassLoader
        // 获取系统类加载器的父类加载器 --> 扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);//PlatformClassLoader@7c30a502
        // 获取扩展类加载器的父类加载器 --> 根加载器(C/C++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//bootstrapClassLoad
        // 测试当前类的加载器
        ClassLoader classLoader = Class.forName("Test").getClassLoader();
        System.out.println(classLoader);//AppClassLoader
        // 测试JDK内置的类加载器
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);//bootstrapClassLoad
        // 如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));
    }
}
JDK11
jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7d907bac
null
jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
null
D:\springcloud\es_kb\target\test-classes;D:\springcloud\es_kb\target\classes;D:\download\apache-maven-3.6.0\repo\org\springframework\boot\

JDK8
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
D:\Java\jdk1.8.0\jre\lib\charsets.jar;
D:\Java\jdk1.8.0\jre\lib\deploy.jar;
D:\Java\jdk1.8.0\jre\lib\ext\access-bridge-64.jar;
D:\Java\jdk1.8.0\jre\lib\ext\cldrdata.jar;
D:\Java\jdk1.8.0\jre\lib\ext\dnsns.jar;
D:\Java\jdk1.8.0\jre\lib\ext\jaccess.jar;
D:\Java\jdk1.8.0\jre\lib\ext\jfxrt.jar;
D:\Java\jdk1.8.0\jre\lib\ext\localedata.jar;
D:\Java\jdk1.8.0\jre\lib\ext\nashorn.jar;
D:\Java\jdk1.8.0\jre\lib\ext\sunec.jar;
D:\Java\jdk1.8.0\jre\lib\ext\sunjce_provider.jar;
D:\Java\jdk1.8.0\jre\lib\ext\sunmscapi.jar;
D:\Java\jdk1.8.0\jre\lib\ext\sunpkcs11.jar;
D:\Java\jdk1.8.0\jre\lib\ext\zipfs.jar;
D:\Java\jdk1.8.0\jre\lib\javaws.jar;
D:\Java\jdk1.8.0\jre\lib\jce.jar;
D:\Java\jdk1.8.0\jre\lib\jfr.jar;
D:\Java\jdk1.8.0\jre\lib\jfxswt.jar;
D:\Java\jdk1.8.0\jre\lib\jsse.jar;
D:\Java\jdk1.8.0\jre\lib\management-agent.jar;
D:\Java\jdk1.8.0\jre\lib\plugin.jar;
D:\Java\jdk1.8.0\jre\lib\resources.jar;
D:\Java\jdk1.8.0\jre\lib\rt.jar;
D:\IDEAWorkspace\demo\anno;
D:\springcloud\es_kb\target\test-classes;D:\springcloud\es_kb\target\classes;D:\download\apache-maven-3.6.0\repo\org\springframework\boot\

为什么 String.class.getClassLoader() 返回null?

Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,并不是一个JAVA类,也就是

无法在java代码中获取它的引用,对Java不可见,所以返回null

可通过 -Xbootclasspath 配置其他目录。

-Xbootclasspath:路径 指定的路径会完全取代jdk核心的搜索路径 坚决不要用
-Xbootclasspath/a:路径 指定的路径会在jdk核心类后搜索 可用
-Xbootclasspath/p:路径 指定的路径会在jdk核心类前搜索 可用,不建议使用

以上配置,如果有多个路径,unix下用“:”分割,windows下用“;”分割。

2. ExtClassLoader

扩展类加载器,加载-Djava.ext.dirs选项指定的目录,比如:%JRE_HOME%\jre\lib**ext**目录下的jar包和class文件。

代码:

public static void main(String[] args) {
    	//DNSNameService 在dnsns.jar包下
        System.out.println(DNSNameService.class.getClassLoader());
    
        //输出 ExtClassLoader 扫描的路径,注意:输出的是文件夹
        String paths = System.getProperty("java.ext.dirs");
        String[] arr = paths.split(":");//如果有多个路径,unix下用“:”,windows下用“;”.
        for(String s : arr){
            System.out.println(s);
        }
    }

3. AppClassLoader

也称为SystemClassLoader(系统类加载器),它的搜索路径由 java.class.path(CLASSPATH) 来指

定,默认是当前文件夹(java命令在哪个目录执行,那么这个目录就是classpath的值),

主要用来加载 自己程序中的类 和 第三方依赖包

代码:

public class test {
    public static void main(String[] args) {
        System.out.println(test.class.getClassLoader());
        System.out.println(test.class.getClassLoader().getParent());
        String path = System.getProperty("java.class.path");
        String[] split = path.split(";");
        for (String s : split) {
            System.out.println(s);
        }
    }
}

上图可以看到,输出中即包含JDK的核心类库,也包含扩展类库,这是因为:

执行java命令时候,指定了classpath参数,把这些jar包都包含了进去。

如何改变默认路径?

运行时指定jvm参数: -classpath /feng/test

如果有多个路径,unix下用“:”分割,windows下用“;”分割。

package com.classloader;


import org.apache.commons.io.ByteOrderMark;
import sun.net.spi.nameservice.dns.DNSNameService;

/**
 * 类的加载:
 *  我们编写的“.java”文件需要经过java编译器编译成“.class”文件,当程序运行时,
 *  如果需要用到某个类,会去加载它,并在内存中创建对应的class对象,这个过程被称为 类的加载。
 *
 *  为什么学习类加载器,在什么工作场景中可以用到?
 *      插件功能,
 *      需求:解析Excel,一般用poi就可以,但是目前Excel模板不确定,而且用户不希望每次增加一种模板都重启应用。
 *      解决:把解析Excel代码抽出来,打成一个jar包,上传到应用中,当用户每次上传Excel时,让他自己选择使用哪个jar包执行,
 *           因为需要动态的加载jar包,这时候就要用到 类加载器,把jar包加载到内存中,通过反射去执行方法,拿到结果。
 *      总之,如果碰到需要动态加载jar包、.class文件这种场景,就应该使用 类加载器。
 *
 * 三大类加载器:
 *  Bootstrap ClassLoader   启动类加载器
 *  ExtClassLoader          扩展类类加载
 *  AppClassLoader          系统类加载器,又叫:应用类加载器
 */
public class Demo {

    public static void main(String[] args) {
        /**
         * 1. Bootstrap ClassLoader   启动类加载器
         *  主要用于加载JDK核心类库,C++语言实现的,加载时的搜索路径是由 sun.boot.class.path 所指定的,
         *  比如:%JRE_HOME%\jre\lib下的rt.jar、resources.jar、charsets.jar等。
         *
         *  -Xbootclasspath:路径	    指定的路径会完全取代jdk核心的搜索路径	坚决不要用
         *  -Xbootclasspath/a:路径	指定的路径会在jdk核心类后搜索	    可用
         *  -Xbootclasspath/p:路径	指定的路径会在jdk核心类前搜索	    可用,不建议使用
         */
//        System.out.println(String.class.getClassLoader());//输出:null,就证明 Bootstrap ClassLoader 加载。
//        String paths = System.getProperty("sun.boot.class.path");
//        String[] arr = paths.split(";");
//        for (String s : arr){
//            System.out.println(s);
//        }
        /**
         * 2. ExtClassLoader          扩展类类加载
         *  扩展类加载器,加载 -Djava.ext.dirs 选项指定的目录,比如:%JRE_HOME%\jre\lib\ext目录下的jar包和class文件。
         *  也可以通过配置 -Djava.ext.dirs 参数设置加载路径
         *
         *  注意:如果配置-Djava.ext.dirs会覆盖Java默认的ext设置。
         */
//        System.out.println(DNSNameService.class.getClassLoader());//输出:null,就证明 Bootstrap ClassLoader 加载。
//        String paths = System.getProperty("java.ext.dirs");
//        String[] arr = paths.split(";");
//        for (String s : arr){
//            System.out.println(s);
//        }
        /**
         * 3. AppClassLoader          系统类加载器,又叫:应用类加载器
         *  它的搜索路径由java.class.path(CLASSPATH)来指定,
         *  默认是当前文件夹(java命令在哪个目录执行,那么这个目录就是classpath的值)
         *  主要用来加载应用程序中的类文件.
         *  AppClassLoader 的父亲类加载器是 ExtClassLoader.
         *
         */
        System.out.println(Demo.class.getClassLoader());
        String paths = System.getProperty("java.class.path");
        String[] arr = paths.split(";");
        for (String s : arr){
            System.out.println(s);
        }
    }

}

4. 类加载器的初始化

从上面可以看出 ExtClassLoader、AppClassLoader都是java对象,而这些对象的生成都是由

Launcher完成的。

Launcher类是java程序的入口,在启动java应用的时候会首先创建Launcher类的对象,创建

Launcher类的时候会创建ExtClassLoader、AppClassLoader,

Launcher的构造方法

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //	创建 ExtClassLoader
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        try {
            //创建 AppClassLoader,指定父加载器是 ExtClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
		//设置当前线程的上下文类加载器是AppClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }
            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }
            System.setSecurityManager(var3);
        }
    }

以上代码可以得出ExtClassLoader是AppClassLoader他爹。

3. 双亲委派模式

为了让上面3个类加载器能够合理的工作,因此有了双亲委派模式。

1. 概念

双亲委派模式:简称:坑爹模式,

即:

  • 如果一个类加载器要加载某个类,它并不会自己先去加载,而是让父加载器去加载,
  • 如果父加载器还有父亲,则进一步向上委托,依次递归,最终将推给顶层的启动类加载器(Bootstrap ClassLoader)。
  • 如果父加载器可以完成加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载。

看下图:

img

这里要注意:Bootstrap ClassLoader并不是ExtClassLoader的父亲,但可以它看作类加载器届的上帝,所有类的

加载都需要先经过Bootstrap ClassLoader,如果加载失败,才自己去加载

2. 源码

加载一个类,最终会走到ClassLoader类中的loadClass方法

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检测是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    	// 父加载器不为空则调用父加载器的loadClass
                        c = parent.loadClass(name, false);
                    } else {
                    	// 父加载器为空则调用Bootstrap Classloader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //父加载器没有找到,则调用findclass,自己去加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

3. 好处

  • 避免类的重复加载

  • 保证java核心api不会被随意替换

    • 假设自定义一个名为java.lang.Integer的类,通过双亲委派模式传递到启动类加载器,
    • 启动类加载器在Java 核心类库,就能找到 Integer 类,于是加载,
    • 不会重加载自定义的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改

举例说明:

package java.lang;

public class Integer {

    public static void test(){
        System.out.println("test");
    }

    public static void main(String[] args) {
        test();
    }
}

输出:

因为加载得是JDK中的Integer类,所以提示找不到main方法。

4. 另一个规则

类的加载还有一个规则:A中使用了B,如果A被AppClassLoader加载,那么B也被 AppClassLoader 加载

但需要注意的是:在加载的时候遵循 坑爹模式

4. 动态加载

1. URLClassLoader

URLClassLoader是ClassLoader的子类,也是ExtClassLoader和AppClassLoader的父类

它可以从指定的 jar 文件和目录中加载类和资源。也就是说,可以动态加载jar包中的类

img

常用的构造方法:

  • URLClassLoader(URL[] urls, ClassLoader parent):使用指定的父加载器创建对象,从指定的urls路径来查询、并加载类。(到实战,解析Excel的时候,什么场景下使用这个构造方法)
  • URLClassLoader(URL[] urls):使用默认的父加载器(AppClassLoader)创建一个ClassLoader对象,从指定的urls路径来查询、并加载类。

2. 实战

新建一个普通的maven项目,

img

建立完成后,应该是:

img

建立两个model:parse、loader

img

在 parse 中新建一个类 ParseExcel

public class ParseExcel {
    public void parse(){
        System.out.println("执行解析Excel方法...........");
    }
}

然后打成jar包:parse.jar。

在 loader 中,新建一个 Test 类,利用 URLClassLoader 运行这个jar包:

public static void main(String[] args) throws Exception {
    // 创建一个URL数组
    File file = new File("/Users/fengxiansheng/Downloads/parse.jar");
    URL[] urls = new URL[]{file.toURI().toURL()};
    //这时候 myClassLoader 的 parent 是 AppClassLoader
    URLClassLoader myClassLoader = new URLClassLoader(urls);
    Class<?> aClass = myClassLoader.loadClass("com.test.ParseExcel");
    Object obj = aClass.newInstance();//利用反射创建对象
    Method method = aClass.getMethod("parse");//获取parse方法
    method.invoke(obj,null);
}

输出:

img

这样做的好处:

  • 修改 parse 不会影响到 loader,只需要把 parse 这个模块打成jar,放在固定的地方
  • 这样 loader 可以24小时不间断运行

注意:这时候 myClassLoader 的 parent 是 AppClassLoader,这点非常重要

img

3. 问题

有些时候,parse中可能引用一些第三方库,比如:jackson-core-2.11.0.jar

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.11.0</version>
</dependency>

并在代码中使用:

package com;

import com.fasterxml.jackson.core.JsonFactory;

public class ParseExcel {

    public void parse(){
        System.out.println("执行解析Excel方法........");
        JsonFactory factory = new JsonFactory();
        System.out.println("执行jsonFaction的getFormatGeneratorFeatures方法:"+factory.getFormatGeneratorFeatures());
    }
}

这时候,在 loader 中运行这个jar:

public static void main(String[] args) throws Exception {
        File file = new File("/Users/fengxiansheng/Downloads/parse.jar");
        File file2 = new File("/Users/fengxiansheng/Downloads/jackson-core-2.11.0.jar");
        URL[] urls = new URL[]{file.toURI().toURL(),file2.toURI().toURL()};
        //这时候 myClassLoader 的 parent 是 AppClassLoader
        URLClassLoader myClassLoader = new URLClassLoader(urls);
        Class<?> aClass = myClassLoader.loadClass("com.test.ParseExcel");
        Object obj = aClass.newInstance();
        Method method = aClass.getMethod("parse");
        method.invoke(obj,null);
    }

这样运行完全没有问题。

但问题是,如果 loader 中也引用了jackson,不过版本是:2.5.4,这时候再次运行:

img

报错,找不到:getFormatGeneratorFeatures() 方法

原因,执行 :myClassLoader.loadClass("com.test.ParseExcel")

  • myClassLoader 的 parent 是 AppClassLoader
  • 根据坑爹模式,myClassLoader 加载 JsonFactory 之前,会先让 AppClassLoader 去加载
  • 而 AppClassLoader 从自己的 classpath 找到了这个类,加载成功,不过版本是2.5.4
  • 但是2.5.4的版本中并没有getFormatGeneratorFeatures方法,所以。。。。

解决:

修改myClassLoader的 parent 为ExtClassLoader,这就要用到 URLClassLoader 的另一个构造方法

img

img

加载过程:

  • myClassLoader 的父亲是 ExtClassLoader,所以这时候 myClassLoader 跟 appClassLoader 是兄弟关系

  • myClassLoader 在加载 ParseExcel 时,会加载 JsonFactory,同时遵循坑爹模式

  • 所以 ExtClassLoader 会尝试加载 JsonFactory 这个类,不会加载成功,转而由 myClassLoader 自己去加载 JsonFactory,加载成功,版本是2.11

  • 2.11 版本的 JsonFactory 有 getFormatGeneratorFeatures 方法,所以正常运行

4. 又一个问题

新建一个 loader-api 的model ,并创建一个接口:MyInterface

img

让 ParseExcel 实现 MyInterface 接口

public class ParseExcel implements MyInterface {

    public void parse(){
        System.out.println("执行解析Excel方法........");
        JsonFactory factory = new JsonFactory();
        System.out.println("执行jsonFaction的getFormatGeneratorFeatures方法:"+factory.getFormatGeneratorFeatures());
    }
}

修改 main 方法,判断 ParseExcel 是否实现了MyInterface

public static void main(String[] args) throws Exception {
        // 创建一个URL数组
        File file = new File("/Users/fengxiansheng/Downloads/parse.jar");
        File file2 = new File("/Users/fengxiansheng/Downloads/jackson-core-2.11.0.jar");
        File file3 = new File("/Users/fengxiansheng/Downloads/loader-api.jar");
        URL[] urls = new URL[]{file.toURI().toURL(),file2.toURI().toURL(),file3.toURI().toURL()};
        URLClassLoader myClassLoader = new URLClassLoader(urls,Test.class.getClassLoader().getParent());
        Class<?> aClass = myClassLoader.loadClass("com.test.ParseExcel");
        //判断是否实现了MyInterface
        if(!MyInterface.class.isAssignableFrom(aClass)){
            System.out.println("该类没有实现 MyInterface 接口,不运行");
            return;
        }
        Object obj = aClass.newInstance();
        Method method = aClass.getMethod("parse");
        method.invoke(obj,null);
    }

输出:

img

为什么会有这样的输出呢?

java 中,所有的 class 文件都会被加载到 jvm 中的元数据区,而且一个class的唯一标识:类加载器+类名

因为 myClassLoader 跟 AppClassLoader 是兄弟关系

  • 执行:myClassLoader.loadClass("com.test.ParseExcel");

    • myClassLoader 会加载 ParseExcel,而 ParseExcel 引用了 MyInterface
    • 所以 myClassLoader 会把 MyInterface,加载到内存中
  • 执行:MyInterface.class.isAssignableFrom(aClass)

    • AppClassLoader 也会把 MyInterface,加载到内存中

      所以此时,内存中有两个 MyInterface 的 class 对象

      img

      再看代码, ParseExcel 实现的是 myClassLoader.MyInterface 接口,所以。。。。。。

      解决:把 myClassLoader 的 parent ,设置为 AppClassLoader,

      ​ 但是会产生找不到:getFormatGeneratorFeatures() 方法

      5. 自定义ClassLoader

      这时候应该自定义类加载器,打破双亲委派模式

      在 loader 中新增 CustomClassLoader

      package com;
      
      import java.net.URL;
      import java.net.URLClassLoader;
      
      /**
       * 自定义类加载器,打破双亲委派模式
       */
      public class CustomClassLoader extends URLClassLoader {
          private URL[] urls;//扫描的jar包路径
      
          public CustomClassLoader(URL[] urls) {
              //1. 指定搜索路径和父加载器:AppClassLoader
              super(urls, CustomClassLoader.class.getClassLoader());
              this.urls = urls;
          }
      
          /**
           * 重载loadClass方法
           * @param name
           * @return
           * @throws ClassNotFoundException
           */
          @Override
          public Class<?> loadClass(String name) throws ClassNotFoundException {
              if (urls != null && urls.length>0) {
                  Class<?> c = null;
                  try {
                      //2. 先在自己的扫描路径中找class
                      c = findClass(name);//该方法存在于URLClassLoader中,如果加载不到指定类,会报ClassNotFoundException
                  }catch (ClassNotFoundException e){
                      //3. 找不到就让 parent 去加载
                      c = this.getParent().loadClass(name);
                  }
                  return c;
              }
              return super.loadClass(name);
          }
      }
      

      坑爹模式是:自己加载在class之前,先让 parent 加载

      我们自定义的类加载器,先在自己的扫描路径中找,找不到会报:ClassNotFoundException

      catch到这个异常,然后让 parent 去找

      修改代码:

      public static void main(String[] args) throws Exception {
          // 创建一个URL数组
          File file = new File("/Users/fengxiansheng/Downloads/parse.jar");
          File file2 = new File("/Users/fengxiansheng/Downloads/jackson-core-2.11.0.jar");
          URL[] urls = new URL[]{file.toURI().toURL(),file2.toURI().toURL()};
          CustomClassLoader myClassLoader = new CustomClassLoader(urls);
          Class<?> aClass = myClassLoader.loadClass("com.test.ParseExcel");
          //判断是否实现了MyInterface
          if(!MyInterface.class.isAssignableFrom(aClass)){
              System.out.println("该类没有实现 MyInterface 接口,不运行");
              return;
          }
          Object obj = aClass.newInstance();
          Method method = aClass.getMethod("parse");
          method.invoke(obj,null);
      }
      

      输出:

      img

      加载过程:

      customClassLoader 父亲是 AppClassLoader

      • 执行:myClassLoader.loadClass("com.test.ParseExcel");

        • customClassLoader 在加载 ParseExcel 时,会加载 MyInterface,并拒绝遵循坑爹模式
        • 先在自己的搜索路径中查找MyInterface,找不到(因为已经去掉了)
        • 所以让它的 parent,也就是 AppClassLoader 去加载,加载成功
      • 执行:MyInterface.class.isAssignableFrom(aClass)

        • AppClassLoader尝试去加载MyInterFace接口,但是因为已经加载成功,所以不需要重复加载
        • 所以 MyInterFace.class.isAssignableFrom(aClass) 返回 true

      5. 总结

      类加载器可以做什么

      • 插件功能
      • 热加载
      • 解决类冲突,一个应用可以使用多个版本的依赖
      • 隔离,同级别中不同的类加载器之间隔离,比如Tomcat容器,每个WebApp有自己的ClassLoader,加载每个WebApp的ClassPath路径上的类。
posted @   MWTYING  阅读(157)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示