从SPI在JDBC中的应用
定义
什么是SPI
- SPI全称是Service Provider Interface(服务提供接口),是jdk提供的服务发现机制,例如JDBC就始终它摆脱了旧版本需要手动注册的动作。
static {
Class.forName("com.mysql.jdbc.Driver")
}
JDBC实现
说明
static {
Class.forName("com.mysql.jdbc.Driver")
}
public class Demo {
//旧版本
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws SQLException {
final String jdbcUrl = "jdbc:mysql://localhost:3306/mysql";
final String userName = "root";
final String password = "root";
try (Connection connection = DriverManager.getConnection(jdbcUrl, userName, password);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user ")) {
while (resultSet.next()){
System.out.println(resultSet.getObject(0));
}
}
}
}
逐步剖析源代码
- 先理清代码执行顺序 Demo=》DriverManager=》Driver =》DriverManager.registerDriver(new Driver)
- loadInitialDrivers 触发静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//会去找META-INF.services.java.sql.Driver
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
//这里会将com.mysql.cj.jdbc.Driver进行实例化,从而触发Driver的静态代码块执行
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
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!");
}
}
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
//如果driver不存在,则注册一个driver到列表里面
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
- DriverManage.getConnection时会从registeredDrivers列表中取driver进行链接
优点
缺点
双亲委派模型
什么是双亲委派模型
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//自己不去加载,调用父亲的loadClass方法,让父亲去加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
SPI破坏了双亲委派模型?
- 是的,因为DriverManager是在rt包下,应该是由顶层的加载器(bootstrap ClaasLoader进行加载),但是双亲委派模型有个缺点(
只能请求父亲加载,不能父亲请求儿子加载
),这样bootstrap加载器会找不到com.mysql.cj.jdbc.Driver
- 为了解决这中情况,
java设计人员
提供了一个办法,在Thread类中,添加contextClassLoader(最底层的类加载器),由这个加载器走双亲委派模型,这样就能完成加载了。
public static <S> ServiceLoader<S> load(Class<S> service) {
//调用当前线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}