zoukankan      html  css  js  c++  java
  • jdbc 加载数据库驱动如何破坏双亲委托模式

    导读
         通过jdbc链接数据库,是每个学习Java web 方向的人必然一开始会写的代码,虽然现在各路框架都帮大家封装好了jdbc,但是研究一下jdbc链接的套路还是很意义
        术语以及相关类
        SPI Service Provider Interface
        classload 类加载器
         AccessController 安全访问类
     
    普通JDBC连接方式(jdk1.8)
    ①: Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
    ②: connection.prepareStatement(sql);
    点击DriverManager类,你会发现,DriverManager初始化时有一个静态代码块要加载
    static {
      loadInitialDrivers();
      println("JDBC DriverManager initialized");
    }
    loadInitialDrivers 方法的部分
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Void run() {
      //这里典型的SPI,Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制
      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;
    }
    });
    jdk早期,数据库驱动如何加载的尼???
    Class.forName(""com.mysql.jdbc.Driver"),
    Connection conn = DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);
    先编程式加载MySQL数据库驱动,其次通过mysql驱动连接数据库
    这2种方式最明显的不同
    以前你需要指定要加载那个数据库驱动,是mysql,oracel还是sqlserver的,现在你只要在maven中引入数据库驱动jar包即可,其他的通过SPI一键搞定,SPI能智能识别到底要调用那个驱动连接数据库,原理很简单,在这个方法中 getConnection(sqlUrl,sqlUserName,sqlPassword);
    getConnection 方法部分片段
    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());
        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());
      }
    }
    isDriverAllowed(aDriver.driver, callerCL) 方法会遍历所有已在maven中引入的驱动,aDriver.driver.connect(url, info); 方法会调用当期的驱动尝试连接数据库,如果能通过此驱动连接数据库成功,就返回,否则继续尝试(各个数据库库驱动如果无法连接数据库, aDriver.driver.connect(url, info); 此方法会返回null)。至此通过SPI 优雅的加载各大厂商的驱动就实现啦,哈哈
    至于SPI 怎么玩,给大家一张图,具体怎么玩百度一下,这里就不多说啦
    为啥说 jdbc 加载数据库驱动如何破坏双亲委托模式???
    现在我们聊聊这中间涉及到一个隐藏知识点,类加载器,java 有以下几大类加载器
    ①:BootStrapClassload
    ②:ExtentionClassload
    ③:ApplicationClassload
    ④:自定义类加载器
    Java 类加载遵循双亲委托模式,用白话说就是 子类类加载类之前都会咨询父类类加载器,这个类属不属于你的呀,你有加载过吗?如果BootStrapClassload,ExtentionClassload 都说我木有加载过,那么ApplicationClassload才会加载
    jdk中具体的具体实现的双亲委托模式的,大家可以参考***.class.getClassLoader().loadClass("");下面的代码就是典型双亲委托模式
    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;
      }
    }
    可能有人问,扯了这么多,跟我们上述JDBC加载驱动有啥关系?
    我很肯定的回答有的哈哈
    Java 类加载器,其实就是分模块加载资源?什么意思尼
     
    Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等
    Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件
    Appclass Loader: 加载当前应用的classpath的所有类。
    强调一下在目前双亲委托模式下,Bootstrap ClassLoader 不可以夸模块加载Appclass Loader。Appclass Loader也不可以夸模块加载Bootstrap ClassLoader,原因很简单,他们在自己的模块路径下中找不到要加载类(比如:com.mysql.jdbc.Driver 这个类,它的类路径是在classpath下,你在rt.jar下下,你怎么找到这个类
     
    那么问题来啦,DriverManager 这个类属于rt.jar 由Bootstrap ClassLoader 来加载的,各大驱动jar包是在classpath下由Appclass Loader 加载的,听着没事问题哈???
    但是,你们仔细看一下代码,你们会发现DriverManager.getConnection(sqlUrl,sqlUserName,sqlPassword);在这个方法中已经完成驱动类的初始化啦,那么就说明这个驱动类是由Bootstrap ClassLoader 加载的???
    这是怎么回事尼???
     
    这里就涉及到了另外一个名词上下文类加载器,对应代码
    Thread.currentThread().getContextClassLoader();
    ClassLoader.getSystemClassLoader();
    这2个方法在普通Java 项目下是相同的,但是在web应用中是不同的,各位有兴趣可以去查一下 
    每个线程都有自己的线程上下文类加载器,并且每个线程的上下文类记载器都是父线程的类加载器,那么我只要考虑第一个线程类加载器就可以啦,因为他是所有的线程的父类,那么对于普通Java程序,谁是第一个线程,这个大家肯定知道 Java 的 Main 方法哈哈,那么main 方法的上下文来加载器是谁尼?? 我们去看一下,main方法的入口类 Launcher 类
    看一下 Launcher 类 初始化
    public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
      var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
      throw new InternalError("Could not create extension class loader", var10);
    }
     
    try {
      this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
      throw new InternalError("Could not create application class loader", var9);
    }
    // 看这里,原来第一个上下文线程类加载器的是AppClassLoader
      Thread.currentThread().setContextClassLoader(this.loader);
      String var2 = System.getProperty("java.security.manager");
    if(var2 != null) {
      SecurityManager var3 = null;
    if(!"".equals(var2) && !"default".equals(var2)) {
    try {
      var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
    } catch (IllegalAccessException var5) {
    ;
    } catch (InstantiationException var6) {
    ;
    } catch (ClassNotFoundException var7) {
    ;
    } catch (ClassCastException var8) {
    ;
    }
    } else {
      var3 = new SecurityManager();
    }
    if(var3 == null) {
      throw new InternalError("Could not create SecurityManager: " + var2);
    }
      System.setSecurityManager(var3);
    }
    }
     
    在看一下通过Java SPI 技术加载数据库驱动的时候,他是直接获取线程上下文类加载器加载数据库驱动(直接通过ApplicationClassload 来加载驱动的),
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    load源码
    public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
    }
    .
    BootStrapClassload偷偷滴指派Appclassload 说帮我加载一下驱动类吧,本来只能由Appclassload委托BootStrapClassload来加载对应的类哈哈, 所以说,JDBC数据库加载破坏了双亲委托模式
     
    结束语
    双亲委托模式不是固定死的,它就是Java指定的一个类加载的一个规则,你完全可以不遵守它,只要你能帮类加载到JVM内存中即可。
  • 相关阅读:
    【原】yield的最基本用法
    【转】C#.net拖拽实现获得文件路径
    【原】.Net之美学习笔记-第1章-1.1.1值类型
    【转】怎样将DataGridView中绑定的表的列名改成中文
    【转】WPF获取外部EXE图标最简单的方法
    【转】C# Excel 导入到 Access数据库表(winForm版)
    【转】SQL2008清除日志
    【原】监视程序运行时间
    【转】MSSQL获取指定表的列名信息,描述,数据类型,长度
    【原】接口
  • 原文地址:https://www.cnblogs.com/huxuhong/p/11856786.html
Copyright © 2011-2022 走看看