声明:本人写的这些,只是当时学习这些知识的理解,不代表是正确的,只是为了巩固记忆,好记忆不如烂笔头,如果哪里有问题,请指出,不喜勿喷,谢谢。

1. 什么是全盘负责委托机制

每个类都有自己的类加载器,那么负责加载当前类的类加载器也会去加载当前类中引用的其他类,前提是引用的类没有被加载过

例如ClassA中有个变量 ClassB,那么加载ClassA的类加载器会去加载ClassB,如果找不到ClassB,则异常。

2. 为什么需要有线程上下文类加载器

jvm推荐我们使用双亲委托机制,主要是保证了相同的类不会被重复加载。但是,在jdk1.2之后,提出了线程上下文类加载器的概念,目的是为了打破双亲委托机制,因为在某些场景下(例如:JNDI,JDBC....)等等SPI场景中,关于什么是SPI(服务发现接口),可以参考之前写的文档资料(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),使用双亲委托机制无法实现,那么为什么无法实现呢?

2.1 使用JDBC的例子,分析为什么双亲委托机制不能实现要求

原生的JDBC的使用,获取数据库连接使用的是 Connection conn = DriverManager.getConnection(xx,xx,xx);很明显,Connection是jdk提供的接口,具体的实现是我们的厂商例如mysql 实现,加入到项目中,那么设想一下,DriverManager.getConnection(xx,xx,xx);该方法肯定是使用的mysql的jar包,返回了mysql实现的Connection对象,那么加载DriverManager类是由启动类加载器加载,根据上面的全盘负责委托机制来说,启动类加载器会去加载MySql的jar包,很明显,找不到。所以使用双亲委托机制来说,无法实现该SPI场景的需求。

2.2 线程上下文类加载器的作用

双亲委托机制:子加载器对应的命名空间包含了父加载器,所以可以实现子容器访问父容器

线程上下文类加载器:使用该类加载器,可以实现 父容器访问子容器场景,主要设置好上下文类加载器即可。

3. 线程上下文类加载器的使用

3.1 线程上下文类加载器使用API

  1. 获取当前线程的上下文类加载器:Thread.currentThread().getContextClassLoader();
  2. 设置当前线程的上下文类加载器:Thread.currentThread().setContextClassLoader(ClassLoader cl);

3.2 线程上下文类加载器的特征

  1. 如果没有设置 setContextClassLoader(),那么线程将继承父线程的上下文类加载器,这段可以通过Thread.init()方法中可以看出
  2. Java应用运行时初始上下文类加载器是系统类加载器,可以在源码:Launcher类的构造方法中,在实例化系统类加载器后,将之设置为上下文类加载器。

3.3 线程上下文类加载器使用的通用写法

  1. ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
  2. try {
  3. Thread.currentThread().setContextClassLoader(null);
  4. //其他执行事件
  5. doSomthing();
  6. } finally {
  7. Thread.currentThread().setContextClassLoader(contextClassLoader);
  8. }

4. 借助JDBC源码分析上下文类加载器的使用

4.1 示例代码

  1. Class.forName("com.mysql.jdbc.Driver");
  2. Connection connection = DriverManager.getConnection("xxx", "xx", "xx");

4.2 源码分析

4.2.1 首先解析第一句 Class.forName("com.mysql.jdbc.Driver");

这里就不介绍了Class.forName()源码了,这行代码表示初始化 Driver类,对Driver类的主动使用,就会导致Driver的静态代码块执行,那么我们进入到Driver类中,看是否有需要初始化调用的静态代码块。

  1. public Driver() throws SQLException {
  2. }
  3. static {
  4. try {
  5. DriverManager.registerDriver(new Driver());
  6. } catch (SQLException var1) {
  7. throw new RuntimeException("Can't register driver!");
  8. }
  9. }

可以看到,存在静态代码块,那么进入到静态代码块中,我们来解析 DriverManager.registerDriver(new Driver());

4.2.2 DriverManager.registerDriver(new Driver());

可能有的朋友查看该代码源码时,直接就会进入到DriverManager类中查看registerDriver(),其实跟解析第一行一行,主动调用类的静态方法,会导致累的初始化,执行 DriverManager中的静态代码块。所以我们需要先看下面的源码:

  1. static {
  2. //首先会添加初始化加载drivers,这里引入了ServiceLoader
  3. loadInitialDrivers();
  4. println("JDBC DriverManager initialized");
  5. }
  6. //因为我们没有设置 jdbc.drivers属性,所以这里只展示关键代码,对于其他不影响流程的代码有所删减,具体的可以看源码
  7. private static void loadInitialDrivers() {
  8. String drivers;
  9. try {
  10. drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
  11. public String run() {
  12. return System.getProperty("jdbc.drivers");
  13. }
  14. });
  15. } catch (Exception ex) {
  16. drivers = null;
  17. }
  18. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  19. public Void run() {
  20. //主要的引入各个厂家的Driver类是的服务是在这里加入的
  21. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  22. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  23. try{
  24. while(driversIterator.hasNext()) {
  25. driversIterator.next();
  26. }
  27. } catch(Throwable t) {
  28. // Do nothing
  29. }
  30. return null;
  31. }
  32. });
  33. }

如果熟悉针对SPI场景的服务加载方式ServiceLoader使用的,当看到这经典的几行的代码时,就知道具体的加载方式了,如果有对ServiceLoader或者SPI不是很熟悉的,可以先阅读或者百度下相关文档(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),那么通过下面这行代码就可以将mysql依赖加载到内存中了。

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

那么是怎么在DriverManager对应的类加载器启动类加载器中加载到mysql jar包的呢,下面来分析 ServiceLaoder.load()方法,go

4.2.3 ServiceLaoder.load()

  1. public static <S> ServiceLoader<S> load(Class<S> service) {
  2. ClassLoader cl = Thread.currentThread().getContextClassLoader();
  3. return ServiceLoader.load(service, cl);
  4. }
  5. public static <S> ServiceLoader<S> load(Class<S> service,
  6. ClassLoader loader)
  7. {
  8. return new ServiceLoader<>(service, loader);
  9. }
  10. public void reload() {
  11. providers.clear();
  12. lookupIterator = new LazyIterator(service, loader);
  13. }
  14. private ServiceLoader(Class<S> svc, ClassLoader cl) {
  15. service = Objects.requireNonNull(svc, "Service interface cannot be null");
  16. loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
  17. acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
  18. reload();
  19. }
  20. //定义的内部类
  21. private class LazyIterator implements Iterator<S>
  22. {
  23. Class<S> service;
  24. ClassLoader loader;
  25. Enumeration<URL> configs = null;
  26. Iterator<String> pending = null;
  27. String nextName = null;
  28. private LazyIterator(Class<S> service, ClassLoader loader) {
  29. this.service = service;
  30. this.loader = loader;
  31. }
  32. private boolean hasNextService() {
  33. if (nextName != null) {
  34. return true;
  35. }
  36. if (configs == null) {
  37. try {
  38. String fullName = PREFIX + service.getName();
  39. if (loader == null)
  40. configs = ClassLoader.getSystemResources(fullName);
  41. else
  42. configs = loader.getResources(fullName);
  43. } catch (IOException x) {
  44. fail(service, "Error locating configuration files", x);
  45. }
  46. }
  47. while ((pending == null) || !pending.hasNext()) {
  48. if (!configs.hasMoreElements()) {
  49. return false;
  50. }
  51. pending = parse(service, configs.nextElement());
  52. }
  53. nextName = pending.next();
  54. return true;
  55. }
  56. private S nextService() {
  57. if (!hasNextService())
  58. throw new NoSuchElementException();
  59. String cn = nextName;
  60. nextName = null;
  61. Class<?> c = null;
  62. try {
  63. c = Class.forName(cn, false, loader);
  64. } catch (ClassNotFoundException x) {
  65. fail(service,
  66. "Provider " + cn + " not found");
  67. }
  68. if (!service.isAssignableFrom(c)) {
  69. fail(service,
  70. "Provider " + cn + " not a subtype");
  71. }
  72. try {
  73. S p = service.cast(c.newInstance());
  74. providers.put(cn, p);
  75. return p;
  76. } catch (Throwable x) {
  77. fail(service,
  78. "Provider " + cn + " could not be instantiated",
  79. x);
  80. }
  81. throw new Error(); // This cannot happen
  82. }
  83. public boolean hasNext() {
  84. if (acc == null) {
  85. return hasNextService();
  86. } else {
  87. PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
  88. public Boolean run() { return hasNextService(); }
  89. };
  90. return AccessController.doPrivileged(action, acc);
  91. }
  92. }
  93. public S next() {
  94. if (acc == null) {
  95. return nextService();
  96. } else {
  97. PrivilegedAction<S> action = new PrivilegedAction<S>() {
  98. public S run() { return nextService(); }
  99. };
  100. return AccessController.doPrivileged(action, acc);
  101. }
  102. }
  103. public void remove() {
  104. throw new UnsupportedOperationException();
  105. }
  106. }

查阅源码,我们可以看出当我们执行 ServiceLaoder.load()的时候,首先会获取当前线程的上下文类加载器。而且在构造方法中也可以看到,如果获取的上下文类加载器为空时,也会使用默认的系统类加载器,而默认设置当前线程的上下文类加载器的时候,默认运行时也是系统类加载器作为上下文类加载器,所以先肯定一点,后续加载类的类加载器肯定是 系统类加载器。

致此,通过 ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);代码我们可以得到待加载的类的父类是Driver类,以及使用系统类加载器来负责加载,这就可以解释了为什么启动类加载器可以加载mysqljar了,因为是使用了系统类加载器来加载的,没有问题,且创建了一个 LazyIterator对象。下面我们将分析如何找到对应的mysql的针对Driver的实现类的。

  1. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  2. try{
  3. while(driversIterator.hasNext()) {
  4. driversIterator.next();
  5. }
  6. } catch(Throwable t) {
  7. // Do nothing
  8. }

4.2.4 driversIterator.hasNext(); driversIterator.next();

从ServiceLoader.iterator()源码中可以看出, driversIterator.hasNext()其实是调用了 load()时候创建的 LazyIterator.hasNext()方法

  1. public Iterator<S> iterator() {
  2. return new Iterator<S>() {
  3. Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
  4. public boolean hasNext() {
  5. if (knownProviders.hasNext())
  6. return true;
  7. return lookupIterator.hasNext();
  8. }
  9. public S next() {
  10. if (knownProviders.hasNext())
  11. return knownProviders.next().getValue();
  12. return lookupIterator.next();
  13. }
  14. public void remove() {
  15. throw new UnsupportedOperationException();
  16. }
  17. };
  18. }

LazyIterator.hasNext() 最终调用了 LazyIterator.hasNextService()方法,致此,我们就可以看到ServiceLoader是如何在指定目录下获取到指定类名对应的实现类全类名信息的。有兴趣的可以看看。

我们的重点是在 LazyIterator.next()方法,该方法最终调用了 LazyIterator.nextService()方法,在该方法中我们可以看到如何将mysql的Driver实现类使用上下文类加载器所加载到内存中。

  1. private S nextService() {
  2. if (!hasNextService())
  3. throw new NoSuchElementException();
  4. //在遍历的时候获取的当前item的文件内容
  5. String cn = nextName;
  6. nextName = null;
  7. Class<?> c = null;
  8. try {
  9. //是否初始化参数为false,表示这里只加载类,不初始化
  10. c = Class.forName(cn, false, loader);
  11. } catch (ClassNotFoundException x) {
  12. fail(service,
  13. "Provider " + cn + " not found");
  14. }
  15. if (!service.isAssignableFrom(c)) {
  16. fail(service,
  17. "Provider " + cn + " not a subtype");
  18. }
  19. try {
  20. //这里才将类初始化,触发mysql Driver类的静态代码块
  21. S p = service.cast(c.newInstance());
  22. providers.put(cn, p);
  23. return p;
  24. } catch (Throwable x) {
  25. fail(service,
  26. "Provider " + cn + " could not be instantiated",
  27. x);
  28. }
  29. throw new Error(); // This cannot happen
  30. }

上面代码说简单点,就是在 hasNext()中找到待实例化的全类名,而在 next()中实例化该类。

注意:这里有一个问题 我们在最之前的代码中,使用了Class.forName()将mysql驱动的Driver类加载了,这里又重复了一次,那我们的第一步岂不是多余的操作。

这个疑问很好,也是正确的,所以在新版本的JDBC处理上,已经使用了SPI方式的ServiceLoader加载方式,不在需要第一步骤的手动加载初始化具体的驱动全类名了。

既然已经触发了mysql Driver累的初始化,那么跟最开始一样,不再重复,这回DriverManager静态代码块已经执行完毕,可以真正的执行 DriverManager.registerDriver(new Driver());了,其实最终也是调用的 DriverManager.registerDriver()

4.2.5 DriverManager.registerDriver();

  1. public static synchronized void registerDriver(java.sql.Driver driver,
  2. DriverAction da)
  3. throws SQLException {
  4. /* Register the driver if it has not already been added to our list */
  5. if(driver != null) {
  6. //加载到缓存中
  7. registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
  8. } else {
  9. // This is for compatibility with the original DriverManager
  10. throw new NullPointerException();
  11. }
  12. println("registerDriver: " + driver);
  13. }

从上述代码上看,其实做的东西很简单,就是判断该Driver如果在缓存中没有就添加到缓存中而已。

致此,第一行代码解析完毕,后面的具体如何获取数据库连接的,跟当前文章想表达的偏离了,所以不再继续了,over

7. 通过JDBC源码来分析线程上下文类加载器以及SPI的使用的更多相关文章

  1. 通过JDBC驱动加载深刻理解线程上下文类加载器机制

    关于线程上下文类加载器已经在之前学得比较透了,作为一个收尾,这里用平常J2EE开发时JDBC连接Mysql数据库常见的一段代码通过分析它的底层进一步加深对线程上下文类加载器的理解,所以先来将连接应用代 ...

  2. 线程上下文类加载器ContextClassLoader内存泄漏隐患

    前提 今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯到两个和线程上下文类加载器Cont ...

  3. 深入理解Java类加载器(二):线程上下文类加载器

    摘要: 博文<深入理解Java类加载器(一):Java类加载原理解析>提到的类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式.在Java ...

  4. JVM 线程上下文类加载器

    当前类加载器(Current ClassLoader) 每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指所依赖的类) 如果ClassX引用了ClassY,那么ClassX的类加载 ...

  5. 【JVM学习笔记】线程上下文类加载器

    有许多地方能够看到线程上下文类加载的设置,比如在sun.misc.Launcher类的构造方法中,能够看到如下代码 先写一个例子建立感性认识 public class Test { public st ...

  6. 【Java虚拟机11】线程上下文类加载器

    前言 目前学习到的类加载的知识,都是基于[双亲委托机制]的.那么JDK难道就没有提供一种打破双亲委托机制的类加载机制吗? 答案是否定的. JDK为我们提供了一种打破双亲委托模型的机制:线程上下文类加载 ...

  7. 线程上下文类加载器(Context ClassLoader)

    1.线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来 ...

  8. 【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程

    核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...

  9. 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证)

    一.前言 在各种Tomcat相关书籍,书上都提到了其类加载器结构: 在Tomcat 7或者8中,共享类和Catalina类加载器在catalina.properties中都是没配置的,请看: 所以,c ...

随机推荐

  1. pycharm/IDEA等 windows 版 常用快捷键

  2. Spring Boot Web 开发@Controller @RestController 使用教程

    在 Spring Boot 中,@Controller 注解是专门用于处理 Http 请求处理的,是以 MVC 为核心的设计思想的控制层.@RestController 则是 @Controller ...

  3. 2016-2017学年第三次测试赛 习题H MCC的考验

    问题 H: MCC的考验 时间限制: 1 Sec  内存限制: 128 MB 题目描述 MCC男神听说新一期的选拔赛要开始了,给各位小伙伴们带来了一道送分题,如果你做不出来,MCC会很伤心的. 给定一 ...

  4. 【网摘】JS 或 jQuery 获取当前页面的 URL 信息

    1.设置或获取对象指定的文件名或路径. window.location.pathname 2.设置或获取整个 URL 为字符串. window.location.href 3.设置或获取与 URL 关 ...

  5. python邮箱发送

    普通发送邮件 使用email模块和stmplib模块,内容比较固定,配好了即可实现,代码如下 一.普通邮箱发送 # -*- coding:utf-8-*- import smtplib from em ...

  6. JavaScript - 编译性还是解释性?

    疑问 在JS的变量和声明式函数的提升看到了"预编译/预处理/预解释"中"预编译"这个字眼,产生了一个疑问:JS是熟知的解释性语言,但JS能被编译吗? 参考 ht ...

  7. js学习:基本数据类型

    数据类型在 js 里面分为两个大类: 基本数据类型 引用数据类型 基本数据类型: 数值 number 各种意义上的数字:整数.小数.浮点数等 正数:100 负数:-100 浮点数,小数:1.234 进 ...

  8. unittest中的parameterized参数化

    一.安装插件 pip install parameterized 二.有默认参数情况与没有默认参数情况---1 注意:这种写法,只能给单个用例进行参数化,不能给多个用例使用,要每个用例都进行参数化. ...

  9. mongodb插入性能

    转自 https://blog.csdn.net/asdfsadfasdfsa/article/details/60872180 MongoDB与MySQL的插入.查询性能测试     7.1  平均 ...

  10. linux 服务器优化 --TIME_WAIT 问题

    linux 服务器优化 --TIME_WAIT 问题: 问题现象: 1.外部机器不能正常连接SSH 2.内向外不能够正常的ping通过,域名也不能正常解析. 通过一些命令,查看服务器TIME_WAIT ...