程序员我爱编程

ClassLoader总结

2018-06-10  本文已影响15人  打伞的Fish

通读这篇文章你会知道如何回答以下问题:


java三大加载器加载的jar位置

知道每个加载器加载什么位置的jar,这对后面分析委托机制会起到作用。
Java语言自带的有三个类加载器

  1. Bootstrap CLassloder

  2. Extention ClassLoader

  3. AppClassLoader
    以下是输出各个加载器的加载jar的位置,这些路径可以通过虚拟机参数进行修改。

    public static void testClassLoader() {
     System.out.println("BootstrapClassLoader:");
     String property = System.getProperty("sun.boot.class.path");
     Arrays.stream(property.split(";")).forEach(System.out::println);
    
     System.out.println("ExtClassLoader :");
     property = System.getProperty("java.ext.dirs");
     Arrays.stream(property.split(";")).forEach(System.out::println);
    
     System.out.println("AppClassLoader :");
     property = System.getProperty("java.class.path");
     Arrays.stream(property.split(";")).forEach(System.out::println);    }
    

加载器之间的关系

Launcher类的classLoader方法初始化如下:
从代码可知Launcher.getClassLoader就是获取appclassLoader,意味着一个类的父加载器如果没有指定,则是默认就是AppClassLoader。

    public Launcher() {
     Launcher.ExtClassLoader var1;
     try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
     } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }
 try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    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);
    }
}
public ClassLoader getClassLoader() {
    return this.loader;
}

但是这个还是无法解释extClassLoader的父加载器是null,请看Laucher类中ExtClassLoader类的构造法方方法,指定的父加载器就是null,所以从parent变量的获取追踪到extClassloader是由于指定null父加载器,所以导致extClassLoader获取父加载器是null,
而AppClassLoader获取父加载器是extClassLoader,是因为指定了extClassLoader为自己的父加载器;

extclassLoader构造方法
appclassLoader构造方法

委托是从下向上,然后具体查找过程却是自上至下

从ClassLoader的loadClass可以明白双亲委托机制过程,同时知道如果自定义的ClassLoader是覆盖findClass,而不是loadClass,采用这种方式进行加载可以避免java核心api中定义的类型被自定义的加载器加载,从而出现多个


ClassLoadere-loadClass

SPI机制简介
SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
SPI具体约定
Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

以下面这段代码来说明双亲委派模型的 逆向加载

    String url = "jdbc:mysql://localhost:3306/mysql";
      //通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");

首先调用静态方法必将导致调用者会进行初始化,所以DriverMananger类将初始化,如果类中有静态代码块必将执行,下面是DriverManager类的代码:


静态代码k loadInitialDrivers方法

因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不是BootrapLoader,那会是什么加载器呢,就是上下文加载,这个可以从load方法中看出:

load

所以这边就可以看出要通过顶层加载器去加载底层加载器的类时,通过上下文加载器实现;

上一篇下一篇

猜你喜欢

热点阅读