Java 双亲委派模型

Java 双亲委派模型

  1. ClassLoader 及 双亲委派模型
  2. 基础知识介绍
    2.1. SPI(Service Provider Interface)
    2.2. ServiceLoader
    2.3. Thread Context ClassLoader
  3. JDBC Driver 注册方式 区别和原理

Java中的ClassLoader

ClassLoader 编程语言 加载内容 Parent ClassLoader
BootstrapClassLoader C++ jre/lib;jre/classes(包括 但不仅限于) Null Java虚拟机启动后初始化
ExtClassLoader Java jre/lib/ext;java.ext.dirs(包括 但不仅限于) BootstrapClassLoader
AppClassLoader Java classpath指定位置 ExtClassLoader ClassLoader.getSystemClassLoader 返回值

Java默认ClassLoader的基本模型:

Java默认ClassLoader委派模型
载录:https://www.cnblogs.com/doit8791/p/5820037.html


基础知识介绍

SPI(Service Provider Interface)

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。
载录:https://blog.csdn.net/sigangjun/article/details/79071850

相关请查阅ServiceLoader

Thread Context ClassLoader

说明:获取ClassLoader的方法

方法 返回值
类对象获取 this.getClass.getClassLoader 加载该Class的ClassLoader
ClassLoder的静态方法 ClassLoader.getSystemClassLoader AppClassLoader

提出问题:扩展类获取底层ClassLoader,只需要通过获取自身加载所用的ClassLoader,然后通过parent即可慢慢找到;但是底层对象(如java.lang.String)想要获取外层ClassLoader(如AppClassLoader或者自定义ClassLoader)该如何实现?

对于AppClassLoader来说,可以通过ClassLoader.getSystemClassLoader来获取,但是自定义ClassLoader就不能这样获取了。
为了解决这个问题,JDK开发者们提供了一个方式,即在Thread中保存一个ClassLoader的引用 – contextClassLoader。即若底层对象想要获取外层ClassLoader,只需在外层首先将ClassLoader设置至Thread中,然后底层对象在从中获取出该ClassLoader即可(这种方式,被评价为对“双亲委派模型”的破坏,不过 也是无奈之举)。

Thread 中的 contextClassLoader,默认情况下为AppClassLoader
在Thread 创建过程中会继承父Thread的 contextClassLoader

private void init(....) {
	Thread parent = currentThread();
	this.contextClassLoader = parent.getContextClassLoader();
}

(Thread 常常被作为一种共享存储|通信媒介)


JDBC Driver 注册方式

  1. new com.mysql.jdbc.Driver();
  2. Class.forName(“com.mysql.jdbc.Driver”);
  3. java.sql.DriverManager.getConnection(“jdbc:mysql://…”);

从实践可以发现,不论是采用第一种方式还是第二种方式,都不会在意对应的返回值。
那么为何还需要调用执行这段逻辑呢? – 用于执行com.mysql.jdbc.Driver中的静态代码块

// from com.mysql.jdbc.Driver
static {
	// 在com.mysql.jdbc包中直接调用java.sql包
	// 两个的ClassLoader是不同的
	// com.mysql.jdbc -- AppClassLoader(正常情况)
	// java.sql -- BootstrapClassLoader
    java.sql.DriverManager.registerDriver(new Driver());
}

// from java.sql.DriverManager
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da)
    throws SQLException {
  	// 将以注册driver信息进行管理
    registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
}

尽然第一种和第二种方式的目的都是为了:执行com.mysql.jdbc.Driver中的静态代码块,那么第三种方式呢?没有显示调用方法来触发com.mysql.jdbc.Driver的静态初始逻辑,那能否成功获取到mysql连接?

在JDBC4.0的升级之后,是可以无需进行显示触发 com.mysql.jdbc.Driver的静态初始逻辑 的。

// from java.sql.DriverManager
static { loadInitialDrivers(); }

private static void loadInitialDrivers() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
        	// 通过ServiceLoader获取配置实现类
        	// 通过迭代返回列表,来load配置实现类,以此来触发Driver的静态逻辑(注册逻辑)
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            while(driversIterator.hasNext()) { driversIterator.next(); }
            return null;
        }
    });

	// 通过-D参数指定的jdbc.drivers属性来load对应Driver,以此来触发Driver的静态逻辑
    String drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
        public String run() { return System.getProperty("jdbc.drivers"); }
    });
    for (String aDriver : drivers.split(":")) 
    { Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); }
}

通过上面的代码展示,可以发现,目前的以无需手动注册Driver,这件事情已经由DriverManager统一处理了,但是 前提是这些Driver都按照规范注册至META-INF/services/java.sql.Driver文件中了

注意实现

现在来说一下其中使用到的Thread ContextClassLoader:
简单分析一下,在java.sql.DriverManager中,通过读取META-INF/services/java.sql.Driver文件,来获取注册的Driver类全限定名称,但是 它该如何去加载这个Driver?

使用自身的ClassLoader – BootstrapClassLoader?这显然是行不通的,因为双亲委派的设计,BootstrapClassLoader是获取不到com.mysql.jdbc.Driver的

这是就Thread 中的contextClassLoader,而这个ClassLoader通常情况下,就是AppClassLoader,正好可以load到com.mysql.jdbc.Driver。完美(虽然破坏了双亲委派模型),以后不需要显示写上Class.forName(“com.mysql.jdbc.Driver”),代码将更加整洁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章