一. SPI 中角色

先上结论
  • DriverManager.getConnection() 内部运用了SPI机制,扫描mysql的jar包的META-INF/services/获取全路径名并使用Class.forName(cn, false, loader),c.newInstance()加载目标驱动。
  • 另外一方面,也解决了为什么不使用Class.forName() 也可以破坏双亲委派,因为getConnection内部封装了Class.forName(cn, false, loader);
  • false 表示 加载字节码返回class对象.但并不去初始化,也就是不触发static代码块
  • c.newInstance 则一定会实例化对象,触发static代码块
前言

以下概念拓展至Effective java page 6 (中文版)

  • SPI (Service Provider Interface),用于拓展工程实例的接口
  • 对于JDBC ,Connection 就是其服务接口的一部分
1.1 服务提供者面向的JDK接口 Driver

Service Interface

JDK 提供了标准,具体的数据库驱动由各大数据库厂商提供

java 为什么要打破双亲委派模型 jdbc打破双亲委派机制_java

1.2 提供者注册 API registerDriver(new Driver)

Provider Registration API

JDK 提供注册用的API,注册的API会调用new Driver()
Driver必须有空参构造方法,为了支持Class.forName().newInstance()

static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}
public Driver() throws SQLException {
		// Required for Class.forName().newInstance()
	}
1.3 用户调用服务的API getConnection(url)

Provider access API

开发人员使用的代码

@CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {
        java.util.Properties info = new java.util.Properties();
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

二. DriverManager 衔接SPI

2.1. DriverManager getConnection的底层源码
private static Connection getConnection(
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
    	// 注册SPI目录下的所有驱动
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        return (con);
                    }
                } 
            } 
        }
    }
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass 
                = Class.forName(driver.getClass().getName(), true, classLoader);
             result = ( aClass == driver.getClass() ) ? true : false;
        }
        return result;
    }

最调用了Class.forName

2.2 DriverManager 使用Class.forName
Thread.currentThread().getContextClassLoader(); // 获取的是AppClassLoader

在加载核心类DriverManager后,可以使用通过线程上下文的 AppClassLoader 加载 SPI实现类

Class.forName(driver.getClass().getName(), true, classLoader);

Class.forName 使用的是 getContextClassLoader() 中的AppClassLoader加载

2.3 DriverManager.getConnection触发静态方法

第一次使用getConnection(String url)会触发 DriverManager 的 static 方法

static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
2.4 DriverManager.loadInitialDrivers()

ServiceLoader<Driver>就是SPI的正在执行容器

private static void loadInitialDrivers() {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                while(driversIterator.hasNext()) {
                   driversIterator.next(); // next() 方法是SPI的最终实现
                }
                return null;
            }
        });
    }

三. SPI 最终实现

物理支持
public final class ServiceLoader<S>implements Iterable<S>{
    private static final String PREFIX = "META-INF/services/";
}

java 为什么要打破双亲委派模型 jdbc打破双亲委派机制_加载_02

ServiceLoader.nextService() 和 com.mysql.jdbc.Driver 的静态方法

ServiceLoader.nextService用于遍历所有SPI(Iterable.next封装了这个方法)

private S nextService() {
            String cn = nextName;//cn是SPI实现者全限定名
            nextName = null;
            Class<?> c = null;
            try {
            	// 加载字节码返回class对象.但并不去初始化,也就是不触发static代码块
                c = Class.forName(cn, false, loader);
            } 
            
            try {
                // cast 不是主要业务逻辑。 c.newInstance触发了 com.mysql.jdbc.Driver的静态方法
                // 静态方法完成了Driver的实例化与注册
                S p = service.cast(c.newInstance());
                providers.put(cn, p);//本地缓存 (全限定名,实现类对象)
                return p;
            } 
        }

Driver 的静态方法被触发,完成将SPI实现类注册到DriverManager

因为是由c.newInstance 触发的,这里的new Driver() 已经被替换成了SPI实现类,注册完成

static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}

四. 梳理getConnection(url) 的注册逻辑

DriverManager.getConnection(url) // 如何取到驱动
  • 步骤一,线程开启, 双亲委派指派核心类加载,DriverManager没有被加载时
  1. Bootstrap ClassLoader 加载
  2. Launcher 加载 AppClassLoader、ExtClassLoader加载
  3. 加载类的**线程通过setContextClassLoader(this.loader)**把 AppClassLoader 设置进上下文
public class Launcher {
    static class AppClassLoader extends URLClassLoader {}
    static class ExtClassLoader extends URLClassLoader {}
    public Launcher() {
    	try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        }
        // 把AppClassLoader设置为本线程上下文的类加载器
        Thread.currentThread().setContextClassLoader(this.loader);
    }
}
  • 步骤二,线程继续执行,DriverManager被加载, 触发loadInitialDrivers(),直接触发SPI读取的工具类实例化
static {
        loadInitialDrivers(); // 内部使用的是ServiceLoader<Driver> 的api
        println("JDBC DriverManager initialized");
    }
// loadInitialDrivers(); 内初始化ServiceLoader
	ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  • 步骤三,ServiceLoader< Driver> 初始化,一路携带AppClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获得的是【步骤一】中设置的AppClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
	
    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }

	// 把中设置的AppClassLoader 赋值给 loader
	private ServiceLoader(Class<S> svc, ClassLoader cl) {
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    }
  • 步骤四,ServiceLoader< Driver> 扫描目录,初始化SPI实现类
public final class ServiceLoader<S>implements Iterable<S>{
    // 规范目录,MySQL驱动的Jar包下一定有此文件,里面是SPI实现类的全路径名
    private static final String PREFIX = "META-INF/services/";
    
    private S nextService() {
        String cn = nextName;//SPI实现者全限定名
        nextName = null;
        Class<?> c = null;
        try {
            //  加载字节码返回class对象.但并不去初始化,也就是不触发static代码块
            c = Class.forName(cn, false, loader);
        } 
		try {
			// cast 不是主要业务逻辑。 c.newInstance触发了 Driver的静态方法
			S p = service.cast(c.newInstance());
			providers.put(cn, p);//本地缓存 (全限定名,实现类对象)
            return p;
        } 
    }
}
  • 步骤五,SPI实现类 com.mysql.jdbc.Driver 初始化后,用 static 调用 DriverManager 注册驱动自身,也就是注册com.mysql.jdbc.Driver完成
static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}

五. 综上,一句话概括

  • DriverManager.getConnection() 内部运用了SPI机制,扫描mysql的jar包的META-INF/services/获取全路径名并使用Class.forName(cn, false, loader),c.newInstance()加载目标驱动。
  • 另外一方面,也解决了为什么不使用Class.forName() 也可以破坏双亲委派,因为getConnection内部封装了Class.forName(cn, false, loader);
  • jvm刚启动的时候不会去加载对应的数据库厂商的驱动,调用第一个获取数据库连接的时候才找对应的Driver实现。用到是扫描SPI的机制,在classpath指定目录下。DriverManager 来自于Boostrap的classloder,直接调用class.forName("");这种情况如果要走双亲委派。**如何调整?**当前的用户线程一定持有的ApplicationClassLoder ,由于JDBC4.0规范问题,jvm大胆得不再询问父类,而是直接用ApplicationClassLoderClass.forName(cn, false, loader)加载classpath下面的class,也就打破了双亲委派。