使用springboot通过spi机制加载mysql驱动的过程

这篇文章主要介绍了使用springboot通过spi机制加载mysql驱动的过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

使用springboot通过spi机制加载mysql驱动的过程,久久派带你了解更多相关信息。

SPI是一种JDK提供的加载插件的灵活机制,分离了接口与实现,就拿常用的数据库驱动来说,我们只需要在spring系统中引入对应的数据库依赖包(比如mysql-connector-java以及针对oracle的ojdbc6驱动),然后在yml或者properties配置文件中对应的数据源配置就可自动使用对应的sql驱动,

比如mysql的配置:

spring:  datasource:    url: jdbc:mysql://localhost:3306/xxxxx?autoReconnect=true&useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai    username: dev    password: xxxxxx    platform: mysql

spi机制正如jdk的classloader一样,你不引用它,它是不会自动加载到jvm的,不是引入了下面的的两个sql驱动依赖就必然会加载oracle以及mysql的驱动:

        <!--oracle驱动-->        <dependency>            <groupId>com.oracle</groupId>            <artifactId>ojdbc6</artifactId>            <version>12.1.0.1-atlassian-hosted</version>        </dependency>         <!--mysql驱动-->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <scope>runtime</scope>        </dependency>

正是由于jdk的这种spi机制,我们在spring项目中使用对应的驱动才这么简单,

我们只需做两件事:

1、在pom文件中引入对应的驱动依赖

2、在配置文件中配置对应的数据源即可

那么在spring项目中到底是谁触发了数据库驱动的spi加载机制呢?为了说明这个问题,咱们先说说jdk的spi的工作机制,jdk的spi通过ServiceLoader这个类来完成对应接口实现类的加载工作,就拿咱们要说的数据库驱动来说,

ServiceLoader会在spring项目的classpath中寻找那些满足下面条件的类:

1、这些jar包的META-INF/services有一个java.sql.Driver的文件

对应java.sql.Driver文件中为该数据库驱动对应的数据库驱动的实现类,比如mysql驱动对应的就是com.mysql.cj.jdbc.Driver,如下图所示:

使用springboot通过spi机制加载mysql驱动的过程

JDK这部分有关SPI具体的实现机制可以阅读下ServiceLoader的内部类LazyIterator,该类的hasNextService、nextService两个方法就是具体SPI机制工作底层机制。

好了,上面简要概述了下JDK的SPI工作机制,下面继续看spring框架如何使用spi机制来完成数据库驱动的自动管理的(加载、注销),接下来就按照事情发展的先后的先后顺序把mysql驱动加载的全过程屡一下,笔者使用的是springboot 2.x,数据源使用的数据源为Hikari,这是后来居上的一款数据源,凭借其优秀的性能以及监控机制成为了springboot 2.x之后首推的数据源,

用过springboot的小伙伴对springboot的自动装载机制,数据源的配置也是使用的自动装配机制,

具体类DataSourceAutoConfiguration

使用springboot通过spi机制加载mysql驱动的过程

注意上面标红部分,这里面引入的Hikari、Tomcat等(除了DataSourceJmxConfiguration之外)都是一些数据源配置,我们先看下

springboot推荐的Hikari数据源配置:

     /**    ** 这是一个Configuration类,该类定义了创建HikariDataSource的Bean方法   ***/  	@Configuration	@ConditionalOnClass(HikariDataSource.class)	@ConditionalOnMissingBean(DataSource.class)	@ConditionalOnProperty(name = \"spring.datasource.type\", havingValue = \"com.zaxxer.hikari.HikariDataSource\",			matchIfMissing = true)	static class Hikari { 		@Bean		@ConfigurationProperties(prefix = \"spring.datasource.hikari\")		public HikariDataSource dataSource(DataSourceProperties properties) {            // 使用配置文件中的数据源配置来创建Hikari数据源			HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);			if (StringUtils.hasText(properties.getName())) {				dataSource.setPoolName(properties.getName());			}			return dataSource;		} 	}

由于在DataSourceAutoConfiguration类中首先引入的就是Hikari的配置,DataSource没有创建,满足ConditionalOnMissingBean以及其他一些条件,就会使用该配置类创建数据源,好了接下来看下createDataSource到底是怎么创建数据源的,

这个过程又是怎么跟SPI关联起来的

abstract class DataSourceConfiguration { 	@SuppressWarnings(\"unchecked\")	protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {        //使用DataSourceProperties数据源配置创建DataSourceBuilder对象(设计模式中的建造者模式)		return (T) properties.initializeDataSourceBuilder().type(type).build();	}     //下面看下DataSourceBuilder的build方法    public T build() {        //在该例子中,type返回的是com.zaxxer.hikari.HikariDataSource类		Class<? extends DataSource> type = getType();        //实例化HikariDataSource类		DataSource result = BeanUtils.instantiateClass(type);		maybeGetDriverClassName();        //bind方法中会调用属性的设置,反射机制,在设置driverClassName属性时		bind(result);		return (T) result;	}     // HikariConfig的方法,HikariDataSource继承自HikariConfig类public void setDriverClassName(String driverClassName)   {      checkIfSealed();       Class<?> driverClass = null;      ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();      try {         if (threadContextClassLoader != null) {            try {                //加载driverClassName对应的类,即com.mysql.cj.jdbc.Driver类,该类为mysql对应的驱动类               driverClass = threadContextClassLoader.loadClass(driverClassName);               LOGGER.debug(\"Driver class {} found in Thread context class loader {}\", driverClassName, threadContextClassLoader);            }            catch (ClassNotFoundException e) {               LOGGER.debug(\"Driver class {} not found in Thread context class loader {}, trying classloader {}\",                            driverClassName, threadContextClassLoader, this.getClass().getClassLoader());            }         }          if (driverClass == null) {            driverClass = this.getClass().getClassLoader().loadClass(driverClassName);            LOGGER.debug(\"Driver class {} found in the HikariConfig class classloader {}\", driverClassName, this.getClass().getClassLoader());         }      } catch (ClassNotFoundException e) {         LOGGER.error(\"Failed to load driver class {} from HikariConfig class classloader {}\", driverClassName, this.getClass().getClassLoader());      }       if (driverClass == null) {         throw new RuntimeException(\"Failed to load driver class \" + driverClassName + \" in either of HikariConfig class loader or Thread context classloader\");      }       try {         // 创建com.mysql.cj.jdbc.Driver对象,接下来看下com.mysql.cj.jdbc.Driver创建对象过程中发生了什么         driverClass.newInstance();         this.driverClassName = driverClassName;      }      catch (Exception e) {         throw new RuntimeException(\"Failed to instantiate class \" + driverClassName, e);      }   }  // com.mysql.cj.jdbc.Driver类public class Driver extends NonRegisteringDriver implements java.sql.Driver {    //    // Register ourselves with the DriverManager    //    static {        try {            //调用DriverManager注册自身,DriverManager使用CopyOnWriteArrayList来存储已加载的数据库驱动,然后当创建连接时最终会调用DriverManager的getConnection方法,这才是真正面向数据库的,只不过spring的jdbc帮助我们屏蔽了这些细节            java.sql.DriverManager.registerDriver(new Driver());        } catch (SQLException E) {            throw new RuntimeException(\"Can\'t register driver!\");        }    }

上面已经来到了DriverManager类,那么DriverManager类里面是否有什么秘密呢,继续往下走,

看下DriverManager的重要方法:

    static {        //静态方法,jvm第一次加载该类时会调用该代码块        loadInitialDrivers();        println(\"JDBC DriverManager initialized\");    }     //DriverManager类的loadInitialDrivers方法     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;        }         AccessController.doPrivileged(new PrivilegedAction<Void>() {            public Void run() {                             //这就是最终的谜底,最终通过ServiceLoader来加载SPI机制提供的驱动,本文用到了两个,一个是mysql的,一个是oracle的,注意该方法只会在jvm第一次加载DriverManager类时才会调用,所以会一次性加载所有的数据库驱动                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.                 */                 //下面的代码就是真正完成数据库驱动加载的地方,对应ServiceLoader类的LazyIterator类,所以看下该类的hasNext一级next方法即可,上面已经讲过,这里就不再赘述                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);            }        }    }

好了,上面已经把springboot如何使用jdk的spi机制来加载数据库驱动的,至于DriverManager的getConnection方法调用过程可以使用类似的方式分析下,在DriverManager的getConnection方法打个断点,当代码停在断点处时,通过Idea或者eclipse的堆栈信息就可以看出个大概了。

但愿本文能帮助一些人了解mysql驱动加载的整个过程,加深对SPI机制的理解。希望能给大家一个参考,也希望大家多多支持趣讯吧。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 55@qq.com 举报,一经查实,本站将立刻删除。转转请注明出处:https://www.szhjjp.com/n/8963.html

(0)
nan
上一篇 2021-07-31
下一篇 2021-07-31

相关推荐

  • 如何跨专业考研(英语专业跨什么专业考研容易)

    如何跨专业考研(英语专业跨什么专业考研容易)很多同学考研时都会选择跨考,可选的专业那么多,到底哪个适合自己呢?今天君君就给大家介绍跨考众多热门选择之一:法律硕士(非法学)为什么选择这个专业的人比较多呢?因为法律硕士(非法学)相对好“跨”,从

    2021-11-27
    0
  • 军婚需要女方什么材料(军婚女方需要符合的条件)

    现在很多人都想保家卫国,不少年轻人都参军了,如果在服役期间想要结婚的话,需要通过严格的政审的,张馨予是我们熟知的明星,她就嫁给了一个军人,那么军婚需要女方什么材料,军婚女方需要符合的条件,军嫂政审不合格的情形有哪些,下面就跟随久久派小编一起来了解一下详细情况吧!军婚需要女方什么材料1、女方的户口本。2、女方的居民身份证。3、女方所在单位婚姻状况证明,或

    2021-08-17
    0
  • 茶颜悦色员工工资多少钱一个月茶颜悦色员工吐槽月薪不超3000元怎么回事

    茶颜悦色员工工资多少钱一个月基本的工资应该是3000元起步,还会有相应的提成,如果经营的比较好,那么相应的工资是上不封顶的。以上就是久久派网»茶颜悦色员工工资多少钱一个月茶颜悦色员工吐槽月薪不超3000元怎么回事的相关内容了,更多精彩请关注作者:久

    2022-01-14
    0
  • 自来水公司呼吁市民每月洗澡2-4次 ! 主要目的是倡导市民节约用水 !

    最近,四川凉山州出现持续晴热高温天气。5月29日,凉山州大部地区出现今年以来的最高气温,个别地方最高气温达到42℃。受高温少雨天气影响,凉山州会东县城镇居民生活饮用水水源告急。当地自来水公司发出呼吁广大市民节约用水的通知,其中一条“过于频繁洗澡并不对皮肤健康有好处,每月以2-4次最为适宜”的建议,引

    热点头条 2023-05-30
    0
  • 中秋国庆这4天加班发3倍工资(10天能赚1月薪水)

    中秋假期已经开始了,很快还会有国庆7天长假,总计有10天假期。如何充分利用这次的假期呢?有人出游、有人探亲,要是不想浪费,利用好的话10天假期就能赚到1个月的工资了。又到了一年一度的如

    2021-09-19
    0
  • 王传君说江疏影有白头发 江疏影幽默地回应:“滚!”

    这一期桃花坞里江疏影忙着张罗购置生活用品和食材,王传君却默默在姐身后来了一句“你真的有白头发”,姐估计也是习惯了,转身回嘴就是一句“滚”!我君哥在这一季先导片还企图澄清他说江疏影“平庸”的事,结果越说越不对,被李雪琴好心阻止!咱就说幸好是多年老朋友了,不然是真的是会被揍的程度!这样的互动不仅展现了明

    热点头条 2023-05-29
    0

发表回复

登录后才能评论