JDBC如何打破双亲委派机制?

1、直接编写测试代码,重点关注这个代码是怎么执行的DriverManager.getConnection()

//这行代码注释也是可以的
//Driver driver = Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/gwmdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8","root", "itsme999");

2、开启debug模式跟踪,最先都会先执行DriverManager类的静态代码块

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

3、进入loadInitialDrivers()方法,把无关代码都省去

private static void loadInitialDrivers() {
        
     	/**....省去上部分代码*/
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

				//需要关注的代码及方法load()
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                
                //需要关注的代码及方法hasNext(),next()
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            }
        });
		/**....省去下部分代码*/
    }

4、然后我们先来分析上述代码的其中这一行代码

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

5ServiceLoader没有静态代码块,所以直接看调用的静态方法load(Driver.class)就行,我们进入load(Driver.class)方法

 public static <S> ServiceLoader<S> load(Class<S> service) {
     ClassLoader cl = Thread.currentThread().getContextClassLoader();
     return ServiceLoader.load(service, cl);
 }

进入可以看到这里Thread.currentThread().getContextClassLoader()这行代码,这个是获取当前线程上下文类加载器,这个线程上下文类加载可以用来设置自己想要的类加载器,默认是系统类加载器,现在我们需要加载Classpath路径下Clazz,当然需要应用类加载器,所以使用默认获取到就可以了

Java默认有三种类加载器(此父类只是说明层次关系):

  • BootstrapClassLoader是嵌在JVM内核加载器,用C++语言编写,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
  • ExtensionClassLoader是用Java编写,且它的父类加载器是BootStrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。我们知道Java中系统属性java.ext.dirs指定的目录由ExtClassLoader加载器加载,如果程序中没有指定该系统属性(-Djava.ext.dirs=sss/lib)那么该加载器默认加载$JAVA_HOME/lib/ext目录下的所有jar文件,通过程序来看下系统变量java.ext.dirs所指定的路径
  • AppClassLoader是应用程序类加载器,负责加载应用程序Classpath目录下的所有jarclass文件。它的父加载器为ExtClassLoader

6、通过线程上下文类加载器帮我们拿到应用类类加载器,这样我们就可以去加载第三方提供的jar包文件,然后进入ServiceLoader.load(service, cl)里面,这里把java.sql.Driver、cl(应用类加载器)两个参数传入进去,然后再跟进ServiceLoader构造方法里面

 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
 
     return new ServiceLoader<>(service, loader);
 }
 
 //ServiceLoader构造方法
 private ServiceLoader(Class<S> svc, ClassLoader cl) {
     /**省去无关代码*/
     
     reload();
 }
 
 //进入reload()方法
 //生成LazyIterator迭代器对象
 public void reload() {
     /**省去无关代码*/
     
     lookupIterator = new LazyIterator(service, loader);
 }

 //进入LazyIterator构造方法
 //赋值类加载器,接口servier(java.sql.Driver.class)
 private LazyIterator(Class<S> service, ClassLoader loader) {
     this.service = service;
     this.loader = loader;
 }

从上述代码可以总结出ServiceLoader.load()就是在帮我们做了三件事情:

通过线程上下文类加载器设置获取到应用类类加载器

生成LazyIterator迭代器对象,后续准备需要使用(hasNext()、next()方法)做铺垫

赋值service=java.sql.Driver.class以及类加载器loader=应用类类累加器,后续准备需要使用
 

7、这样这行代码ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);就分析完了,然后在回到第3步的第二行代码,开始要执行迭代器了,代码虽短却蕴藏着东西

while(driversIterator.hasNext()) {
	driversIterator.next();
}

8、跟踪进入hasNext()方法,代码如下,发现内部其实是调用hasNextService()方法,然后我们在看hasNextService()内部执行逻辑,可以发现该hasNextService()方法其实主要是用来查找META-INF/services/文件夹下面配置的实现类包全路径的内容,如果有信息就读取进去,然后就可以调用next()(其实是nextService()方法),next()方后面会讲到用来干啥的,其实是用来加载Class类及创建Class的对象

ServiceLoader的成员变量PREFIX

private static final String PREFIX = "META-INF/services/";

此图片发现在mysql包里面配置确实存在写死了这个配置文件,到了这就里,其实明白为什么我们可以省略Class.forName("com.mysql.jdbc.Driver")这个步骤,还是可以成功加载该类
在这里插入图片描述
hasNext()方法里面具体调用的是hasNextServicehasNextService就是用来查找配置信息,代码如下,具体只需要关注hasNextService方法

public boolean hasNext() {
 	//省去无关代码..
 	
 	return hasNextService();
 	
 	//省去无关代码...
}


private boolean hasNextService() {
	 /** 
	 *	PREFIX = "META-INF/services/"
	 *	service.getName()=java.sql.Driver 
	 *	拼接全路径能够找到这个java.sql.Driver的文件配置的内容
	 */
	 String fullName = PREFIX + service.getName();
	
	 //通过路径URL更全面信息(不用在意这里)
	 configs = loader.getResources(fullName);
	   
	 while ((pending == null) || !pending.hasNext()) {
	     //通过IO流读取配置信息 com.mysql.jdbc.Driver
	     pending = parse(service, configs.nextElement());
	 }

	 //这里如果上面发现有配置信息,就说明找到了包路径了,接下来就要开始加载这个Clazz了,
	 //那么我们就开始要调用next方法,来实例化我们的Clazz,那么next里到底是怎么做的呢?(反射)
	 nextName = pending.next();  
}

经过hasNext()方法之后最终得到了下面截图的结果,获取到了两个第三方的全路径信息,接下来就是要遍历这个ArrayList里面的两个值,开始通过反射机制生成Clazz实例,集合遍历可以使用迭代器,那么执行遍历pending.next()代码,得到的第一个肯定是com.msql.jdbc.Driver包路径,所以代此时变量nextName被赋值为com.msql.jdbc.Driver,因为next()方法里面需要使用这个全局变量
在这里插入图片描述

9、开始进入pending.next();代码,执行迭代器遍历上述的ArrayList,发现其实是调用的nextService()方法,然后在进入nextService()方法,其实就是拿到了包路径(nextName),开始通过反射在进行Clazz实例化并创建其对象

nextName = pending.next();
public S next() {
	//省去无关代码...
	
	return nextService();
	
	//省去无关代码...
}

在进入nextService()方法,这个方法很重要很重要,每一行代码都有东西

private S nextService() {
     //省去无关代码...
     	
	 String cn = nextName;
	 nextName = null;
	 Class<?> c = null;
	 
	 //class类实例化,这里因为设置了false,不会调用类的clinit()方法
	 c = Class.forName(cn, false, loader);
	
	 //创建class的对象,这里创建对象会调用clinit()方法
     S p = service.cast(c.newInstance());
     
     //省去无关代码...     
}

首先分析这一行Class.forName()代码,我们知道主动类加载,会触发clinit()方法执行,也就是静态代码块首先会被执行(但是这里没有执行,是因为第二个参数设置为false,关闭了静态代码初始化,所以就不会去调用clinit()),

此时我们知道cn就是上文的nextName=com.mysql.jdbc.Driver全路径,loader就是一开始的ServiceLoader帮我们准备好的应用类类加载器,这样我们就可以加载到这个com.mysql.jdbc.Driver

c = Class.forName(cn, false, loader);

这里我们会发现com.mysql.jdbc.Driver是有静态代码块的,那么这个静态代码块什么时候执行呢?其实就是在创建Driver类的对象的时候执行的,我们看看这一行代码,看到newInstance就应该很明白了,就是在创建对象了,同时也这里也会执行静态代码块

S p = service.cast(c.newInstance());

c.newInstance()会调用下面的static代码块方法下面我们一起看看这个静态代码块做了啥事?就是干了一件事情,就是将我们的从第三方jar包中加载到的Driver类添加到registeredDrivers集合中,后面调用getConnection()方法需要使用

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
         DriverManager.registerDriver(new Driver());
    }
}

public class DriverManager {
	private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) {

         registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    }
}

到此为止,我们在DriverManager的静态代码块中的loadInitialDrivers()方法的事情就全部做完了,总结下来其实就是设置应用类加载器,加载第三方jar包(META-INF/services/java.sql.Driver),然后注册Driver实例即可

connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/gwmdb?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8","root", "itsme999");

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

10、最后在看看getConnection()方法,上面驱动类已经有了,获取连接就简单多了,通过驱动获取到Connection即可

for(DriverInfo aDriver : registeredDrivers) {
	//url: jdbc:mysql://localhost:3306/gwmdb?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8
	//info就是封装的用户名、密码
	 Connection con = aDriver.driver.connect(url, info);
}

这里我们使用到了一个很有用的东西就是线程上线文类加载,默认是应用类加载器,该加载器还允许我们自己设置需要的类加载,然后就可以直接通过该类加载器进行加载clazzjar包文件,打破了传统的双亲委派机制
 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值