使用springboot通過(guò)spi機(jī)制加載mysql驅(qū)動(dòng)的過(guò)程
SPI是一種JDK提供的加載插件的靈活機(jī)制,分離了接口與實(shí)現(xiàn),就拿常用的數(shù)據(jù)庫(kù)驅(qū)動(dòng)來(lái)說(shuō),我們只需要在spring系統(tǒng)中引入對(duì)應(yīng)的數(shù)據(jù)庫(kù)依賴(lài)包(比如mysql-connector-java以及針對(duì)oracle的ojdbc6驅(qū)動(dòng)),然后在yml或者properties配置文件中對(duì)應(yīng)的數(shù)據(jù)源配置就可自動(dòng)使用對(duì)應(yīng)的sql驅(qū)動(dòng),
比如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機(jī)制正如jdk的classloader一樣,你不引用它,它是不會(huì)自動(dòng)加載到j(luò)vm的,不是引入了下面的的兩個(gè)sql驅(qū)動(dòng)依賴(lài)就必然會(huì)加載oracle以及mysql的驅(qū)動(dòng):
<!--oracle驅(qū)動(dòng)--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>12.1.0.1-atlassian-hosted</version> </dependency> <!--mysql驅(qū)動(dòng)--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
正是由于jdk的這種spi機(jī)制,我們?cè)趕pring項(xiàng)目中使用對(duì)應(yīng)的驅(qū)動(dòng)才這么簡(jiǎn)單,
我們只需做兩件事:
1、在pom文件中引入對(duì)應(yīng)的驅(qū)動(dòng)依賴(lài)
2、在配置文件中配置對(duì)應(yīng)的數(shù)據(jù)源即可
那么在spring項(xiàng)目中到底是誰(shuí)觸發(fā)了數(shù)據(jù)庫(kù)驅(qū)動(dòng)的spi加載機(jī)制呢?為了說(shuō)明這個(gè)問(wèn)題,咱們先說(shuō)說(shuō)jdk的spi的工作機(jī)制,jdk的spi通過(guò)ServiceLoader這個(gè)類(lèi)來(lái)完成對(duì)應(yīng)接口實(shí)現(xiàn)類(lèi)的加載工作,就拿咱們要說(shuō)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)來(lái)說(shuō),
ServiceLoader會(huì)在spring項(xiàng)目的classpath中尋找那些滿(mǎn)足下面條件的類(lèi):
1、這些jar包的META-INF/services有一個(gè)java.sql.Driver的文件
對(duì)應(yīng)java.sql.Driver文件中為該數(shù)據(jù)庫(kù)驅(qū)動(dòng)對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)的實(shí)現(xiàn)類(lèi),比如mysql驅(qū)動(dòng)對(duì)應(yīng)的就是com.mysql.cj.jdbc.Driver,如下圖所示:
JDK這部分有關(guān)SPI具體的實(shí)現(xiàn)機(jī)制可以閱讀下ServiceLoader的內(nèi)部類(lèi)LazyIterator,該類(lèi)的hasNextService、nextService兩個(gè)方法就是具體SPI機(jī)制工作底層機(jī)制。
好了,上面簡(jiǎn)要概述了下JDK的SPI工作機(jī)制,下面繼續(xù)看spring框架如何使用spi機(jī)制來(lái)完成數(shù)據(jù)庫(kù)驅(qū)動(dòng)的自動(dòng)管理的(加載、注銷(xiāo)),接下來(lái)就按照事情發(fā)展的先后的先后順序把mysql驅(qū)動(dòng)加載的全過(guò)程屢一下,筆者使用的是springboot 2.x,數(shù)據(jù)源使用的數(shù)據(jù)源為Hikari,這是后來(lái)居上的一款數(shù)據(jù)源,憑借其優(yōu)秀的性能以及監(jiān)控機(jī)制成為了springboot 2.x之后首推的數(shù)據(jù)源,
用過(guò)springboot的小伙伴對(duì)springboot的自動(dòng)裝載機(jī)制,數(shù)據(jù)源的配置也是使用的自動(dòng)裝配機(jī)制,
具體類(lèi)DataSourceAutoConfiguration
注意上面標(biāo)紅部分,這里面引入的Hikari、Tomcat等(除了DataSourceJmxConfiguration之外)都是一些數(shù)據(jù)源配置,我們先看下
springboot推薦的Hikari數(shù)據(jù)源配置:
/** ** 這是一個(gè)Configuration類(lèi),該類(lèi)定義了創(chuàng)建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) { // 使用配置文件中的數(shù)據(jù)源配置來(lái)創(chuàng)建Hikari數(shù)據(jù)源 HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } }
由于在DataSourceAutoConfiguration類(lèi)中首先引入的就是Hikari的配置,DataSource沒(méi)有創(chuàng)建,滿(mǎn)足ConditionalOnMissingBean以及其他一些條件,就會(huì)使用該配置類(lèi)創(chuàng)建數(shù)據(jù)源,好了接下來(lái)看下createDataSource到底是怎么創(chuàng)建數(shù)據(jù)源的,
這個(gè)過(guò)程又是怎么跟SPI關(guān)聯(lián)起來(lái)的
abstract class DataSourceConfiguration { @SuppressWarnings("unchecked") protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) { //使用DataSourceProperties數(shù)據(jù)源配置創(chuàng)建DataSourceBuilder對(duì)象(設(shè)計(jì)模式中的建造者模式) return (T) properties.initializeDataSourceBuilder().type(type).build(); } //下面看下DataSourceBuilder的build方法 public T build() { //在該例子中,type返回的是com.zaxxer.hikari.HikariDataSource類(lèi) Class<? extends DataSource> type = getType(); //實(shí)例化HikariDataSource類(lèi) DataSource result = BeanUtils.instantiateClass(type); maybeGetDriverClassName(); //bind方法中會(huì)調(diào)用屬性的設(shè)置,反射機(jī)制,在設(shè)置driverClassName屬性時(shí) bind(result); return (T) result; } // HikariConfig的方法,HikariDataSource繼承自HikariConfig類(lèi) public void setDriverClassName(String driverClassName) { checkIfSealed(); Class<?> driverClass = null; ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader(); try { if (threadContextClassLoader != null) { try { //加載driverClassName對(duì)應(yīng)的類(lèi),即com.mysql.cj.jdbc.Driver類(lèi),該類(lèi)為mysql對(duì)應(yīng)的驅(qū)動(dòng)類(lèi) 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 { // 創(chuàng)建com.mysql.cj.jdbc.Driver對(duì)象,接下來(lái)看下com.mysql.cj.jdbc.Driver創(chuàng)建對(duì)象過(guò)程中發(fā)生了什么 driverClass.newInstance(); this.driverClassName = driverClassName; } catch (Exception e) { throw new RuntimeException("Failed to instantiate class " + driverClassName, e); } } // com.mysql.cj.jdbc.Driver類(lèi) public class Driver extends NonRegisteringDriver implements java.sql.Driver { // // Register ourselves with the DriverManager // static { try { //調(diào)用DriverManager注冊(cè)自身,DriverManager使用CopyOnWriteArrayList來(lái)存儲(chǔ)已加載的數(shù)據(jù)庫(kù)驅(qū)動(dòng),然后當(dāng)創(chuàng)建連接時(shí)最終會(huì)調(diào)用DriverManager的getConnection方法,這才是真正面向數(shù)據(jù)庫(kù)的,只不過(guò)spring的jdbc幫助我們屏蔽了這些細(xì)節(jié) java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
上面已經(jīng)來(lái)到了DriverManager類(lèi),那么DriverManager類(lèi)里面是否有什么秘密呢,繼續(xù)往下走,
看下DriverManager的重要方法:
static { //靜態(tài)方法,jvm第一次加載該類(lèi)時(shí)會(huì)調(diào)用該代碼塊 loadInitialDrivers(); println("JDBC DriverManager initialized"); } //DriverManager類(lèi)的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() { //這就是最終的謎底,最終通過(guò)ServiceLoader來(lái)加載SPI機(jī)制提供的驅(qū)動(dòng),本文用到了兩個(gè),一個(gè)是mysql的,一個(gè)是oracle的,注意該方法只會(huì)在jvm第一次加載DriverManager類(lèi)時(shí)才會(huì)調(diào)用,所以會(huì)一次性加載所有的數(shù)據(jù)庫(kù)驅(qū)動(dòng) 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. */ //下面的代碼就是真正完成數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載的地方,對(duì)應(yīng)ServiceLoader類(lèi)的LazyIterator類(lèi),所以看下該類(lèi)的hasNext一級(jí)next方法即可,上面已經(jīng)講過(guò),這里就不再贅述 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); } } }
好了,上面已經(jīng)把springboot如何使用jdk的spi機(jī)制來(lái)加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)的,至于DriverManager的getConnection方法調(diào)用過(guò)程可以使用類(lèi)似的方式分析下,在DriverManager的getConnection方法打個(gè)斷點(diǎn),當(dāng)代碼停在斷點(diǎn)處時(shí),通過(guò)Idea或者eclipse的堆棧信息就可以看出個(gè)大概了。
但愿本文能幫助一些人了解mysql驅(qū)動(dòng)加載的整個(gè)過(guò)程,加深對(duì)SPI機(jī)制的理解。希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud_Eureka服務(wù)注冊(cè)與發(fā)現(xiàn)基礎(chǔ)及構(gòu)建步驟
Eureka服務(wù)注冊(cè)中心,主要用于提供服務(wù)注冊(cè)功能,當(dāng)微服務(wù)啟動(dòng)時(shí),會(huì)將自己的服務(wù)注冊(cè)到Eureka Server,這篇文章主要介紹了SpringCloud中Eureka的配置及詳細(xì)使用,需要的朋友可以參考下2023-01-01java實(shí)現(xiàn)二維碼掃碼授權(quán)登陸
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)二維碼掃碼授權(quán)登陸,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10SpringMVC中@ModelAttribute與@RequestBody的區(qū)別及說(shuō)明
這篇文章主要介紹了SpringMVC中@ModelAttribute與@RequestBody的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11解決Java導(dǎo)入excel大量數(shù)據(jù)出現(xiàn)內(nèi)存溢出的問(wèn)題
今天小編就為大家分享一篇解決Java導(dǎo)入excel大量數(shù)據(jù)出現(xiàn)內(nèi)存溢出的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06Java實(shí)現(xiàn)英文句子中的單詞順序逆序輸出的方法
這篇文章主要介紹了Java實(shí)現(xiàn)英文句子中的單詞順序逆序輸出的方法,涉及java字符串遍歷、判斷、截取、輸出等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01