从dubbo到|从dubbo到 jdbc 与 spi
最近在看Dubbo源码,dubbo的功能是基于扩展点(Extension)的,如果想要修改哪个模块,可以很方便的进行扩展替换。
这种扩展点就是借鉴的spi的思想,但是dubbo并没有使用jdk原生的serviceLoader,而是自己实现了ExtensionLoader来加载扩展点,支持键值对,更为灵活,遵循的规范基本相同。这是题外话。
什么是SPI?SPI能干什么?这里有篇介绍文章--链接
最初接触到SPI的时候有些困惑,查资料发现很多文章都拿jdbc作为SPI的典型例子。
回忆当初我刚上大学的时候,hibernate、mybatis这类框架还没大火,只能自己写jdbc链接数据库的代码,是这样的
try {
Class.forName("com.mysql.jdbc.Driver");
//1
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/EScore", "root", "root");
//2
pst = conn.prepareStatement("SELECT COUNT(1) FROM score");
ResultSet resultSet = pst.executeQuery();
resultSet.next();
System.out.println(resultSet.getInt(1));
} catch (Exception e) {
e.printStackTrace();
}
这个Class.forName("com.mysql.jdbc.Driver")作用就是加载数据库驱动类com.mysql.jdbc.Driver,代码如下
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
加载时会先执行上面代码中的静态代码块,通过
DriverManager.registerDriver(new Driver());
com.mysql.jdbc.driver new了一个自己的实例,并注册给了DriverManager,这样DriverManager就能够使用驱动程序去获得数据库连接。
到此为止,驱动已经加载完毕,没有任何关于SPI的应用,我也是一头雾水,不明白SPI跟jdbc有啥关系。但是一想,如果jdbc使用到了spi,那么在DriverManager中一定会有相应实现,继续看DriverManager代码,有静态块如下
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
loadInitialDrivers()方法代码如下
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
注意到这个方法里调用了ServiceLoader,来加载驱动文件
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
终于找到了SPI的身影,继续看会发现驱动类的加载是在遍历的时候进行的
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
loadedDrivers.iterator()返回的是
private LazyIterator lookupIterator;
LazyIterator 是ServiceLoader的内部私有类,实现了terator接口,代码如下
// Private inner class implementing fully-lazy provider lookup
//
private class LazyIterator
implements Iterator
{Class service;
ClassLoader loader;
Enumeration configs = null;
Iterator pending = null;
String nextName = null;
private LazyIterator(Class service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn+ " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error();
// This cannot happen
}public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService();
}
};
return AccessController.doPrivileged(action, acc);
}
}public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService();
}
};
return AccessController.doPrivileged(action, acc);
}
}public void remove() {
throw new UnsupportedOperationException();
}}
可以看到nextService是会用Class.forName去加载驱动类,然后执行驱动类中静态块,然后DriverManager注册一个实例。。。。那么既然DriverManager使用类spi的机制去自动加载所有驱动类,我们在写代码的时候就无需再去
Class.forName("com.mysql.jdbc.Driver");
//1
是的,这行代码已经多余了,去掉之后仍然可以正常建立数据库连接,爽
【从dubbo到|从dubbo到 jdbc 与 spi】
推荐阅读
- 2018-02-06第三天|2018-02-06第三天 不能再了,反思到位就差改变
- 一个小故事,我的思考。
- Docker应用:容器间通信与Mariadb数据库主从复制
- 第三节|第三节 快乐和幸福(12)
- 你到家了吗
- 一个人的碎碎念
- 遇到一哭二闹三打滚的孩子,怎么办┃山伯教育
- 死结。
- 我从来不做坏事
- 赢在人生六项精进二阶Day3复盘