Java 双亲委派模型
- ClassLoader 及 双亲委派模型
- 基础知识介绍
2.1. SPI(Service Provider Interface)
2.2. ServiceLoader
2.3. Thread Context ClassLoader - 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的基本模型:
基础知识介绍
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的 contextClassLoaderprivate void init(....) { Thread parent = currentThread(); this.contextClassLoader = parent.getContextClassLoader(); }
(Thread 常常被作为一种共享存储|通信媒介)
JDBC Driver 注册方式
- new com.mysql.jdbc.Driver();
- Class.forName(“com.mysql.jdbc.Driver”);
- 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”),代码将更加整洁。