【java_基础深入】com.mysql.jdbc.Driver 借助SPI打破双亲委派

23 篇文章 2 订阅
5 篇文章 0 订阅

一. 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 提供了标准,具体的数据库驱动由各大数据库厂商提供

在这里插入图片描述

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

关于getContextClassLoader,点击此链接,拉至文章底部

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/";
}

在这里插入图片描述

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,也就打破了双亲委派。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值