java spi在jdbc中的使用讲解

SPI

JDBC连接数据库时为什么要Class.forName()问题

  • 在使用Class.forName(com.mysql.jdbc.Driver);的时候会加载这个Driver类,这个Driver类中有静态代码块调用DriverManager.registerDriver(new Driver());语句注册驱动,静态代码块在加载类的时候就会执行,因此利用反射加载这个类就可以完成注册。

    而DriverManager类里面有属性private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>(),这个集合保存了的是DriverInfo类型,这个类型包括注册的驱动和一个对应的data信息,这个data信息直接注册其实为null::

    //源码

    • //源码
      public static synchronized void registerDriver(java.sql.Driver driver)
              throws SQLException {
      //这里用的是java里面的Driver接口接收jar包里面的Driver对象,因为它实现了这个接口
              registerDriver(driver, null);//直接是null!!!
          }
      
      public static synchronized void registerDriver(java.sql.Driver driver,
                  DriverAction da)
              throws SQLException {
      
              /* Register the driver if it has not already been added to our list */
              if(driver != null) {
                  registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
              } else {
                  // This is for compatibility with the original DriverManager
                  throw new NullPointerException();
              }
      
              println("registerDriver: " + driver);
      
          }
      ————————————————
      版权声明:本文为CSDN博主「开局一打酒。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
      原文链接:https://blog.csdn.net/w55935/article/details/123315870
      

    然后在使用DriverManager.getConnection()得到连接的时候底层是遍历registeredDrivers集合得到一个DriverInfo对象aDriver,然后用aDriver对象调用这个对象里面接收驱动的属性driver,这个driver就是我们new出来传入的,再用这个driver调用connect方法:

    • for(DriverInfo aDriver : registeredDrivers) {
          // If the caller does not have permission to load the driver then
          // skip it.
          if(isDriverAllowed(aDriver.driver, callerCL)) {
              try {
                  println("    trying " + aDriver.driver.getClass().getName());
       	//下面这句话就是得到这个我们传入的驱动再调connect!!!!!!!!!!!!!!!!
                  Connection con = aDriver.driver.connect(url, info);
                  if (con != null) {
                      // Success!
                      println("getConnection returning " + aDriver.driver.getClass().getName());
                      return (con);
                  }
              } catch (SQLException ex) {
                  if (reason == null) {
                      reason = ex;
                  }
              }
      
          } else {
              println("    skipping: " + aDriver.getClass().getName());
          }
      
      }
      ————————————————
      版权声明:本文为CSDN博主「开局一打酒。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
      原文链接:https://blog.csdn.net/w55935/article/details/123315870
      

    由上可知,我们虽然不推荐用直接new Driver()得到Driver对象再调用connect方法得到连接,但是用DriverManager得到连接的时候底层仍然是在Class.forName()new一个Driver对象注册进去,然后找出这个对象用它调用connect方法来得到连接。因此使用Class.forName()注册驱动必不可少,不然DriverManager里面的驱动集合里找不到它,无法进行连接。

JDBC为什么要破坏双亲委派模型#

问题背景

在JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。

这个自动加载采用的技术叫做SPI,数据库驱动厂商也都做了更新。可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。

使用上,我们只需要通过下面一句就可以创建数据库的连接:

Copy

Connection con =    
             DriverManager.getConnection(url , username , password ) ;   
问题解答

因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。

JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,**根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。**也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。

查看DriverManager类的源码,看到在使用DriverManager的时候会触发其静态代码块,调用 loadInitialDrivers() 方法,并调用ServiceLoader.load(Driver.class) 加载所有在META-INF/services/java.sql.Driver 文件里边的类到JVM内存,完成驱动的自动加载。

Copy

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

    private static void loadInitialDrivers() {

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

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

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

    }

Copy

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

这个子类加载器是通过 Thread.currentThread().getContextClassLoader() 得到的线程上下文加载器。

那么这个上下文类加载器又是什么呢?

Copy

public Launcher() {
    ...
    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    Thread.currentThread().setContextClassLoader(this.loader);
    ...
}

可以看到,在 sun.misc.Launcher 初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,所以线程上下文类加载器默认情况下就是系统加载器

注: 原文链接:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值