/

ClassLoader原理以及SPI使用

ClassLoader

基本概念

ClassLoader,翻译过来就是类加载器。顾名思义,其主要的作用就是将Class文件加载到JVM中生成Class对象。

类加载的过程主要分为3步:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区的数据储存格式由虚拟机实现自行定义。然后在内存中创建一个java.lang.Class类的对象,作为程序访问方法区中的这些类型数据的外部接口。

class文件详解

  • 文件格式

class文件格式

其中u2, u4, u8分别表示2个字节,4个字节,8个字节。info结尾的是由多个无符号数或者其他表作为数据项构成的复合数据。

  • cp_info

    cp_info

cp_info中这些不同的类型常量使用的字节大小也不相同。

涉及到的info信息过多,不再一一赘述,具体可以看下周志明写的《深入理解Java虚拟机》的第6章关于class的讲解。


由于各个类型的数据之间没有分割符,所以类文件中的字节数据都是严格按上面表格顺序来的。例如开头4个字节为固定的魔数cafebabe,后面紧跟着2个字节是次版本号,再后面2个字节是主版本号。我们以一个简单的代码示例来简单分析下数据。

public class Hello {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

编译以上代码生成.class文件后用sublime打开,显示为十六进制表示如下:

cafe babe 0000 0034 001d 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000a 4865 6c6c 6f2e
6a61 7661 0c00 0700 0807 0017 0c00 1800
1901 000b 6865 6c6c 6f20 776f 726c 6407
001a 0c00 1b00 1c01 0005 4865 6c6c 6f01
0010 6a61 7661 2f6c 616e 672f 4f62 6a65
6374 0100 106a 6176 612f 6c61 6e67 2f53
7973 7465 6d01 0003 6f75 7401 0015 4c6a
6176 612f 696f 2f50 7269 6e74 5374 7265
616d 3b01 0013 6a61 7661 2f69 6f2f 5072
696e 7453 7472 6561 6d01 0007 7072 696e
746c 6e01 0015 284c 6a61 7661 2f6c 616e
672f 5374 7269 6e67 3b29 5600 2100 0500
0600 0000 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0009 000b 000c 0001 0009 0000 0025
0002 0001 0000 0009 b200 0212 03b6 0004
b100 0000 0100 0a00 0000 0a00 0200 0000
0300 0800 0400 0100 0d00 0000 0200 0e

分析上诉字节码:

  • cafe babe 0000 0034 魔数 (cafebabe), JDK次版本 (0) ,JDK主版本 (52) 52表示JDK8
  • 001d 常量池大小, 16 + 13 = 29。后面跟常量池数据(共包含29-1=28个常量)
  • 0a00 0600 0f 第一个常量,类型为CONSTANT_Methodref_info,5字节大小。第一个字节为0a(表示类中方法的符号引用,即CONSTANT_Methodref_info),第2,3字节00 06表示指向声明方法的类描述符的索引(CONSTANT_Class_info),第4,5字节00 0f表示指向名称及类型描述符(CONSTANT_NameAndType)。
  • 09 0010 0011 第二个常量,类型为CONSTANT_Fieldref_info,5字节大小。第一个字节为09(表示字段的符号引用,即CONSTANT_Fieldref_info),第2,3字节 0010 表示指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项,第4,5字节 00 11 表示指向字段描述符CONSTANT_NameAndType的索引项。

挨个解析起了过于繁琐,我们可以利用JDK自带的工具javap反编译字节码,生成较为直观的数据。

javap -p -v Hello.class

Classfile /Users/jianlin/dev/coding/java/Hello.class
  Last modified 2018-5-22; size 415 bytes
  MD5 checksum cb7981a0b53212d3f3005746f14b245a
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // hello world
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Hello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Hello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               hello world
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Hello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
}
SourceFile: "Hello.java"

可以看到Constant pool前2个常量跟我们分析的是对应的,分别为Methodref跟Fieldref,其中Methodref的第一个参数CONSTANT_Class_info定位到第6个常量,而第6个常量又定位到第22个常量,最终表示为java/lang/Object。

我们尝试改下字节码,可以看到打印的字符串hello world在第18个常量处,为了能快速定位到字节码中的位置,将hello world转成16进制数据为:68 65 6c 6c 6f 20 77 6f 72 6c 64,直接查找,定位到字节码第10行6865 6c6c 6f20 776f 726c 64。为了保证字节码其他参数不变,我们将其替换成相同长度的其他字符串。将其改成68 69 20 20 6c 69 6e 6a 69 61 6e(hi linjian)。替换完成后重新执行,发现已经输出变成hi linjian了。

字节码的修改从侧面能说明字节码增强技术的可行性,我们只需要保证增强后的class文件的数据符合字节码的规范即可。

类加载过程

类从被加载到虚拟机内存中开始,到被卸载出内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用和卸载。

类加载流程

主要谈下加载

类的加载过程需要ClassLoader来完成,java提供的3个ClassLoader,分别是Bootstrap ClassLoader(引导类加载器), Extension ClassLoader(扩展类加载器),Application ClassLoader(应用类加载器)。这3个ClassLoader分别加载不同范围的类库:

  • Bootstrap ClassLoader 加载$JAVA_HOME/lib目录下的,能被虚拟机识别的类库,虚拟机会按照文件名进行识别。可以通过System.getProperty("sun.boot.class.path")来查询哪些类库会由Bootstrap ClassLoader进行加载。
  • Extension ClassLoader 加载$JAVA_HOME/lib/ext目录下的类库,这个没有限制,可以将其他jar包扔进去,发现通过XX.class.getClassLoader()方法打印的结果变成扩展类加载器。
  • Application ClassLoader 加载用户类路径(ClassPath)上指定的类库

以上类加载器有一个层级关系,称为双亲委派模型。

双亲委派模型要求除了顶层的Bootstrap ClassLoader,其余的类加载器都应该有自己的父类加载器。这里类加载器之间的父子关系不是用继承来实现的,而是通过组合关系服用父加载器的代码。

每个类由CLassLoader加载时,先检查是否已经被加载过①,如果没有则先交给自己的上一层类加载器来进行加载②,如果上层类加载器已经加载过了,则之间返回,否则抛出ClassNotFoundException异常后③,再调用自己的findClass()方法进行加载④。这个结合源代码好理解些:

//ClassLoader


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 {
                    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;
        }
    }

实际上我们配置ClassPath环境变量时,将rt.jar配置到ClassPath里,加载其中的类时,当前默认的类加载器一般为Application ClassLoader,根据双亲委托规则,最终会交由Bootstrap ClassLoader类库进行加载。所以双亲委托保证了String,Object等核心类库不会因为自己重新定义一个同名类而被替换破坏。

SPI

概念

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。

一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。

通俗来讲就是我们只是保留了一个接口的调用,程序运行时需要将该接口具体实现的类通过ClassLoader加载到JVM中,然后接口的调用就可以转变成具体的实现调用。从而到达在程序运行时动态给接口寻找接口的具体实现。

应用实例

  1. 首先定义一个接口,表示展示试图的样式,然后利用SPI调用view方法
package spi;

/**
 * Created by jianlin on 06/14/2018.
 */
public interface View {

    void view();

}
package spi;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * Created by jianlin on 06/14/2018.
 */
public class Main {

    public static void main(String[] args) {
        ServiceLoader<View> s = ServiceLoader.load(View.class);
        Iterator<View> iterator = s.iterator();
        while (iterator.hasNext()) {
            View view = iterator.next();
            view.view();
        }
    }

}

SPI主要实现需要用到ServiceLoader,这个后面再具体分析该类实现原理。

2.定义2种具体的实现类(当然不一定非要定义2种,定义一种也可以)

package spi;

/**
 * Created by jianlin on 06/14/2018.
 */
public class HtmlView implements View{

    @Override
    public void view() {
        System.out.println("html view");
    }
}
package spi;

/**
 * Created by jianlin on 06/14/2018.
 */
public class TextView implements View{

    @Override
    public void view() {
        System.out.println("text view");
    }
}

3.声明SPI文件信息,将具体实现类的全限定名作为内容,多个实现类以换行隔开,文件名称以服务接口命名

文件名:spi.View,内容:

spi.HtmlView
spi.TextView

4.将上述具体实现类按照SPI规范打成jar文件(即包含META-INF/services以及类信息)

先将实现类的.class文件,service信息等放到一个目录中,目录结构如下:

.
├── META-INF
│   └── services
│       └── spi.View
└── spi
    ├── HtmlView.class
    └── TextView.class

然后用jdk自带的jar指令进行打包,生成一个view.jar:

jar cvf view.jar .

5.将view.jar加入classpath中,运行Main方法

.
├── spi
│   ├── Main.class
│   └── View.class
└── view.jar

java -classpath $CLASS_PATH:view.jar spi.Main

得到输出结果如下:

html view
text view

所以我们要采用其他的具体实现类只需要替换一个jar包即可。

###原理

主要实现逻辑在ServiceLoader类中,下面来分析下ServiceLoader的加载具体实现类的过程。

public class Main {

    public static void main(String[] args) {
        ServiceLoader<View> s = ServiceLoader.load(View.class); //A
        Iterator<View> iterator = s.iterator(); //B
        while (iterator.hasNext()) {  //C
            View view = iterator.next(); //D
            view.view();
        }
    }

}

A. ServiceLoader提供了一个load方法返回指定service类型的ServiceLoader,湖区当前Thread的获取当前线程所使用的类加载器,同service一起做为参数调ServiceLoader的另一个带ClassLoader的方法。

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

具体实例化链路如下,主要目的是初始化一个带LazyIterator成员变量的ServiceLoader对象,此时并没有开始解析META配置文件。

ServiceLoader.load(service) -> ServiceLoader.load(service, cl) -> new ServiceLoader<>(service, loader) -> new LazyIterator(service, loader)

对Thread.currentThread().getContextClassLoader()有疑问的可以看下R大的解答。

Thread context class loader存在的目的主要是为了解决parent delegation机制下无法干净的解决的问题。假如有下述委派链: ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader 那么委派链左边的ClassLoader就可以很自然的使用右边的ClassLoader所加载的类。 但如果情况要反过来,是右边的ClassLoader所加载的代码需要反过来去找委派链靠左边的ClassLoader去加载东西怎么办呢?没辙,parent delegation是单向的,没办法反过来从右边找左边。 这种情况下就可以把某个位于委派链左边的ClassLoader设置为线程的context class loader,这样就给机会让代码不受parent delegation的委派方向的限制而加载到类了。

B.利用A步骤中实例化的LazyIterator对象来构建ServiceLoader的一个迭代器,lookupIterator就是ServiceLoader中LazyIterator对象变量名。

public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

C.执行LazyIterator#hasNextService方法来解析META目录中的文件,fullName是由常量PREFIX(META-INF/services/) 跟接口的全限定名拼接成的,这就是SPI要求的文件名称以接口的全限定名命名的原因。将解析的类名存放在LazyIterator中的String类型的成员变量nextName中。这里可以看到利用之前的ClassLoader#getResources方法来将文件中的内容解析到configs中。这里迭代器pending会在LazyIterator第一次迭代时初始化,通过parse方法读取文件来返回一个集合[spi.HtmlView, spi.TextView]。故nextName在第一次执行完hasNextService后赋值为spi.HtmlView。

//LazyIterator成员变量

Class<S> service; //步骤A中赋值为View.class
ClassLoader loader; //步骤A中赋值为当前线程中获取的ClassLoader
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;



private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

D.步骤C中已经拿到类的全限定名nextName,通过Class.forName来将Class文件通过ClassLoader加载到JVM中并且生产Class对象,还是通过前面获取的ClassLoader来加载类。得到Class对象后通过newInstance()方法实例化对象,存放到providers map中,然后返回实例化的对象,这时我们就已经拿到View接口实现类的对象了,可以执行实现类的具体方法。

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

原理解析完了后,基本搞明白了SPI的约定的缘由。

SPI约定:

  • 在META-INF/services/目录中创建以Service接口全限定名命名的文件,该文件内容为Service接口具体实现类的全限定名,文件编码必须为UTF-8。 //参考步骤C,之所以UTF-8是因为parse方法中读取文件时写死了编码是utf-8
  • 使用ServiceLoader.load(Class class); 动态加载Service接口的实现类。//加载流程看上述ABCD步骤解析
  • 如SPI的实现类为jar,则需要将其放在当前程序的classpath下。//为了能成功读取文件
  • Service的具体实现类必须有一个不带参数的构造方法。//因为c.newInstance()

经典案例JDBC

说到SPI的运用,大家经常会想到JDBC,下面看看我们常用的JDBC到底是如何运用SPI的(以MySQL作为case进行分析)。

SPI用到的ServiceLoader类是JDK1.6引入的,而MySQL驱动包实现SPI(添加META/services/java.sql.Driver文件)发生在5.1.16+版本之后。所以我们先看下在JDK1.5版本以及mysql-connector-java-5.1.15.jar是如何实现mysql jdbc驱动的加载过程的。

旧版jdbc加载(非SPI方式)

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

早期我们使用jdbc的时候都是需要先执行一次Class.forName(“com.mysql.jdbc.Driver”),通过Class.forName加载Class文件会同时执行类的初始化逻辑,执行static静态块里的代码。

看下com.mysql.jdbc.Driver的代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

执行了DriverManager的registerDriver方法,将当前类的实例化对象作为参数传递进去了。找到jdk1.5的rt.jar包反编译看下之前DriverManager的实现。

package java.sql;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.security.AccessController;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import sun.security.action.GetPropertyAction;

public class DriverManager
{
  static final SQLPermission SET_LOG_PERMISSION = new SQLPermission("setLog");

  /* Error */
  public static PrintWriter getLogWriter()
  {
    // Byte code:
    //   0: getstatic 249    java/sql/DriverManager:logSync    Ljava/lang/Object;
    //   3: dup
    //   4: astore_0
    //   5: monitorenter
    //   6: getstatic 248    java/sql/DriverManager:logWriter    Ljava/io/PrintWriter;
    //   9: aload_0
    //   10: monitorexit
    //   11: areturn
    //   12: astore_1
    //   13: aload_0
    //   14: monitorexit
    //   15: aload_1
    //   16: athrow
    // Line number table:
    //   Java source line #70    -> byte code offset #0
    //   Java source line #71    -> byte code offset #6
    //   Java source line #72    -> byte code offset #12
    // Local variable table:
    //   start    length    slot    name    signature
    //   4    10    0    Ljava/lang/Object;    Object
    //   12    4    1    localObject1    Object
    // Exception table:
    //   from    to    target    type
    //   6    11    12    finally
    //   12    15    12    finally
  }

  public static void setLogWriter(PrintWriter paramPrintWriter)
  {
    SecurityManager localSecurityManager = System.getSecurityManager();
    if (localSecurityManager != null) {
      localSecurityManager.checkPermission(SET_LOG_PERMISSION);
    }
    synchronized (logSync)
    {
      logStream = null;
      logWriter = paramPrintWriter;
    }
  }

  public static synchronized Connection getConnection(String paramString, Properties paramProperties)
    throws SQLException
  {
    ClassLoader localClassLoader = getCallerClassLoader();

    return getConnection(paramString, paramProperties, localClassLoader);
  }

  public static synchronized Connection getConnection(String paramString1, String paramString2, String paramString3)
    throws SQLException
  {
    Properties localProperties = new Properties();

    ClassLoader localClassLoader = getCallerClassLoader();
    if (paramString2 != null) {
      localProperties.put("user", paramString2);
    }
    if (paramString3 != null) {
      localProperties.put("password", paramString3);
    }
    return getConnection(paramString1, localProperties, localClassLoader);
  }

  public static synchronized Connection getConnection(String paramString)
    throws SQLException
  {
    Properties localProperties = new Properties();

    ClassLoader localClassLoader = getCallerClassLoader();

    return getConnection(paramString, localProperties, localClassLoader);
  }

  public static synchronized Driver getDriver(String paramString)
    throws SQLException
  {
    println("DriverManager.getDriver(\"" + paramString + "\")");
    if (!initialized) {
      initialize();
    }
    ClassLoader localClassLoader = getCallerClassLoader();
    for (int i = 0; i < drivers.size(); i++)
    {
      DriverInfo localDriverInfo = (DriverInfo)drivers.elementAt(i);
      if (getCallerClass(localClassLoader, localDriverInfo.driverClassName) != localDriverInfo.driverClass) {
        println("    skipping: " + localDriverInfo);
      } else {
        try
        {
          println("    trying " + localDriverInfo);
          if (localDriverInfo.driver.acceptsURL(paramString))
          {
            println("getDriver returning " + localDriverInfo);
            return localDriverInfo.driver;
          }
        }
        catch (SQLException localSQLException) {}
      }
    }
    println("getDriver: no suitable driver");
    throw new SQLException("No suitable driver", "08001");
  }

  public static synchronized void registerDriver(Driver paramDriver)
    throws SQLException
  {
    if (!initialized) {
      initialize();
    }
    DriverInfo localDriverInfo = new DriverInfo();
    localDriverInfo.driver = paramDriver;
    localDriverInfo.driverClass = paramDriver.getClass();
    localDriverInfo.driverClassName = localDriverInfo.driverClass.getName();
    drivers.addElement(localDriverInfo);
    println("registerDriver: " + localDriverInfo);
  }

  public static synchronized void deregisterDriver(Driver paramDriver)
    throws SQLException
  {
    ClassLoader localClassLoader = getCallerClassLoader();
    println("DriverManager.deregisterDriver: " + paramDriver);

    DriverInfo localDriverInfo = null;
    for (int i = 0; i < drivers.size(); i++)
    {
      localDriverInfo = (DriverInfo)drivers.elementAt(i);
      if (localDriverInfo.driver == paramDriver) {
        break;
      }
    }
    if (i >= drivers.size())
    {
      println("    couldn't find driver to unload");
      return;
    }
    if (getCallerClass(localClassLoader, localDriverInfo.driverClassName) != localDriverInfo.driverClass) {
      throw new SecurityException();
    }
    drivers.removeElementAt(i);
  }

  public static synchronized Enumeration<Driver> getDrivers()
  {
    Vector localVector = new Vector();
    if (!initialized) {
      initialize();
    }
    ClassLoader localClassLoader = getCallerClassLoader();
    for (int i = 0; i < drivers.size(); i++)
    {
      DriverInfo localDriverInfo = (DriverInfo)drivers.elementAt(i);
      if (getCallerClass(localClassLoader, localDriverInfo.driverClassName) != localDriverInfo.driverClass) {
        println("    skipping: " + localDriverInfo);
      } else {
        localVector.addElement(localDriverInfo.driver);
      }
    }
    return localVector.elements();
  }

  public static void setLoginTimeout(int paramInt)
  {
    loginTimeout = paramInt;
  }

  public static int getLoginTimeout()
  {
    return loginTimeout;
  }

  @Deprecated
  public static synchronized void setLogStream(PrintStream paramPrintStream)
  {
    SecurityManager localSecurityManager = System.getSecurityManager();
    if (localSecurityManager != null) {
      localSecurityManager.checkPermission(SET_LOG_PERMISSION);
    }
    logStream = paramPrintStream;
    if (paramPrintStream != null) {
      logWriter = new PrintWriter(paramPrintStream);
    } else {
      logWriter = null;
    }
  }

  @Deprecated
  public static PrintStream getLogStream()
  {
    return logStream;
  }

  public static void println(String paramString)
  {
    synchronized (logSync)
    {
      if (logWriter != null)
      {
        logWriter.println(paramString);

        logWriter.flush();
      }
    }
  }

  private static Class getCallerClass(ClassLoader paramClassLoader, String paramString)
  {
    Class localClass = null;
    try
    {
      localClass = Class.forName(paramString, true, paramClassLoader);
    }
    catch (Exception localException)
    {
      localClass = null;
    }
    return localClass;
  }

  private static void loadInitialDrivers()
  {
    String str1;
    try
    {
      str1 = (String)AccessController.doPrivileged(new GetPropertyAction("jdbc.drivers"));
    }
    catch (Exception localException1)
    {
      str1 = null;
    }
    println("DriverManager.initialize: jdbc.drivers = " + str1);
    if (str1 == null) {
      return;
    }
    while (str1.length() != 0)
    {
      int i = str1.indexOf(':');
      String str2;
      if (i < 0)
      {
        str2 = str1;
        str1 = "";
      }
      else
      {
        str2 = str1.substring(0, i);
        str1 = str1.substring(i + 1);
      }
      if (str2.length() != 0) {
        try
        {
          println("DriverManager.Initialize: loading " + str2);
          Class.forName(str2, true, ClassLoader.getSystemClassLoader());
        }
        catch (Exception localException2)
        {
          println("DriverManager.Initialize: load failed: " + localException2);
        }
      }
    }
  }

  private static synchronized Connection getConnection(String paramString, Properties paramProperties, ClassLoader paramClassLoader)
    throws SQLException
  {
    if (paramClassLoader == null) {
      paramClassLoader = Thread.currentThread().getContextClassLoader();
    }
    if (paramString == null) {
      throw new SQLException("The url cannot be null", "08001");
    }
    println("DriverManager.getConnection(\"" + paramString + "\")");
    if (!initialized) {
      initialize();
    }
    Object localObject = null;
    for (int i = 0; i < drivers.size(); i++)
    {
      DriverInfo localDriverInfo = (DriverInfo)drivers.elementAt(i);
      if (getCallerClass(paramClassLoader, localDriverInfo.driverClassName) != localDriverInfo.driverClass) {
        println("    skipping: " + localDriverInfo);
      } else {
        try
        {
          println("    trying " + localDriverInfo);
          Connection localConnection = localDriverInfo.driver.connect(paramString, paramProperties);
          if (localConnection != null)
          {
            println("getConnection returning " + localDriverInfo);
            return localConnection;
          }
        }
        catch (SQLException localSQLException)
        {
          if (localObject == null) {
            localObject = localSQLException;
          }
        }
      }
    }
    if (localObject != null)
    {
      println("getConnection failed: " + localObject);
      throw ((Throwable)localObject);
    }
    println("getConnection: no suitable driver");
    throw new SQLException("No suitable driver", "08001");
  }

  static void initialize()
  {
    if (initialized) {
      return;
    }
    initialized = true;
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
  }

  private static Vector drivers = new Vector();
  private static int loginTimeout = 0;
  private static PrintWriter logWriter = null;
  private static PrintStream logStream = null;
  private static boolean initialized = false;
  private static Object logSync = new Object();

  private static native ClassLoader getCallerClassLoader();
}

registerDriver方法通过传进来的Driver实力对象构建了个一个DriverInfo 实体bean,存入了drivers集合。

接着看DriverManager的获取jdbc连接方法getConnection(String url, String username, String password)

  public static synchronized Connection getConnection(String paramString1, String paramString2, String paramString3)
    throws SQLException
  {
    Properties localProperties = new Properties();

    ClassLoader localClassLoader = getCallerClassLoader();
    if (paramString2 != null) {
      localProperties.put("user", paramString2);
    }
    if (paramString3 != null) {
      localProperties.put("password", paramString3);
    }
    return getConnection(paramString1, localProperties, localClassLoader);
  }

  private static native ClassLoader getCallerClassLoader();

方法内部通过getCallerClassLoader()本地方法获取当前Class.forName()调用者的类加载。然后遍历之前添加过DriverInfo实体bean的drivers集合(jdbc的实现可能有多种,如果oracle也调用registerDriver方法进行注册,那么drivers集合会存在多个jdbc的实现,这时会通过遍历drivers集合,依次获取连接,如果获取的连接不为空则直接返回,否则返回null接着遍历(一般各个实现包都有根据jdbc:mysql://这种前缀做check,不匹配直接返回null)。

private static Class getCallerClass(ClassLoader paramClassLoader, String paramString)
  {
    Class localClass = null;
    try
    {
      localClass = Class.forName(paramString, true, paramClassLoader);
    }
    catch (Exception localException)
    {
      localClass = null;
    }
    return localClass;
  }

getCallerClass方法就是再加载一次com.mysql.jdbc.Driver,利用当前调用者的ClassLoader,具体用途我也不是很清楚,不过看当前版本的DriverManger里的注释写着:

// If the caller does not have permission to load the driver then
// skip it.

翻译就是确保当前获取connection的类加载器有权限使用之前加载jdbc的具体Driver类的ClassLoader加载的类,是一个鉴权判断。

新版jdbc加载(SPI方式)

Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

不需要显示声明Class.forName("com.mysql.jdbc.Driver");,这些SPI已经帮我们做了。直接DriverManager#getConnection会促发DriverManager类初始化,执行static静态块里的代码:

 /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}



 private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

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

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

里面用到了之前讲的ServiceLoader,会读取ClassPath目录中的jar包,解析里面的META-INF/services/java.sql.Driver文件,然后利用Class.forName实例化具体Driver的实现类,在实现类中,会将自己的实现类通过java.sql.DriverManager.registerDriver()方法注册到DriverManger里,一个registeredDrivers变量。

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

后面获取连接的逻辑跟旧版一样,还是依次遍历registeredDrivers集合,找到匹配的connection。

第一次码这么多字,继续加油~

参考:

关于Thread.currentThread().getContextClassLoader()

关于Class.forName(“com.mysql.jdbc.Driver”)

Why java.sql.DriverManager.getCallerClassLoader() is native?