概述
前言
虽然我自己在前段时间再总结一些Java知识,但是经过最近的面试发现,很多自己掌握的并不牢靠,所以决定把原来很多内容拆分出来一部分一部分自己写,这篇主要在梳理一遍Java的SPI 机制吧。温故而知新,可以为师矣。
介绍
Java SPI 全程为 Service Provider Interface,直译过来就是 服务提供商接口。我理解的概念的话就是,由JDK语言开发组制定一系列功能接口,但功能的具体实现是由各个服务商自行提供。这也满足的依赖倒置原则。依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程。
具体事例
最熟悉的SPI服务应该就是JDBC了
在java.util.sql中定义了对于数据库功能的各个接口,以及数据库操作生命周期中各对象的接口
如Driver表示数据库的驱动器,Connection表示一次数据库的连接
我们在使用第三方实现的时候我们一般是直接通过java.util.sql中的DriverManager来获取具体的第三方Driver或者Connection,那么DriverManager是如何加载到第三方数据库的呢?
接下来我们就好好梳理一下从接口Class文件加载到第三方实现的加载,以及第三方实现的调用的完整流程,看一下DriverManager的源码
环境
jdk1.8.0_144
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
加载SPI第三方实现
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
//.... 此处省略一些不重要的内容
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
//关键是这里的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
//此处使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader来加载具体实现类,打破了双亲委派机制
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
再看一下第三方的Driver实现类中都干了些什么,此处以com.mysql.jdbc的Driver为例
static {
try {
//将自己的Driver实例注册进java.util.sql.DriverManager的CopyOnWriteArrayList列表中,供后期使用
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
当SPI接口调用第三方的功能实现,此处以JDBC的getConnection为例
try {
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","test","123456");
} catch (SQLException e) {
e.printStackTrace();
}
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
//此处返回的是直接调用DriverManager的类,一般为我们自己的程序类
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//此处为我们自己程序类的类加载器 一般为AppClassLoader
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
//此处省略一些不重要的内容
for(DriverInfo aDriver : registeredDrivers) {
//由AppClassLoader检查是否已经装载了第三方驱动类
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
//此处省略一些不重要的内容
}
总结
到目前为止我们已经弄清楚,由BootstrapClassloader加载的协议接口类,如何打破双亲委派机制 来加载AppClassLoader才可以加载到的classpath中的第三方实现类
主要方式为
-
使用ClassLoader.getSystemClassLoader来获取AppClassLoader
Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
-
虽然实际使用中没有用到,使用Thread.currentThread().getContextClassLoader()获取AppClassLoader
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
最后
以上就是老实小鸭子为你收集整理的Java SPI机制前言的全部内容,希望文章能够帮你解决Java SPI机制前言所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复