SPI是一種JDK提供的加載插件的靈活機制,分離了接口與實現(xiàn),就拿常用的數(shù)據(jù)庫驅(qū)動來說,我們只需要在spring系統(tǒng)中引入對應(yīng)的數(shù)據(jù)庫依賴包(比如mysql-connector-java以及針對oracle的ojdbc6驅(qū)動),然后在yml或者properties配置文件中對應(yīng)的數(shù)據(jù)源配置就可自動使用對應(yīng)的sql驅(qū)動,
比如mysql的配置:
1
2
3
4
5
6
|
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一樣,你不引用它,它是不會自動加載到j(luò)vm的,不是引入了下面的的兩個sql驅(qū)動依賴就必然會加載oracle以及mysql的驅(qū)動:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!--oracle驅(qū)動--> < dependency > < groupId >com.oracle</ groupId > < artifactId >ojdbc6</ artifactId > < version >12.1.0.1-atlassian-hosted</ version > </ dependency > <!--mysql驅(qū)動--> < dependency > < groupId >mysql</ groupId > < artifactId >mysql-connector-java</ artifactId > < scope >runtime</ scope > </ dependency > |
正是由于jdk的這種spi機制,我們在spring項目中使用對應(yīng)的驅(qū)動才這么簡單,
我們只需做兩件事:
1、在pom文件中引入對應(yīng)的驅(qū)動依賴
2、在配置文件中配置對應(yīng)的數(shù)據(jù)源即可
那么在spring項目中到底是誰觸發(fā)了數(shù)據(jù)庫驅(qū)動的spi加載機制呢?為了說明這個問題,咱們先說說jdk的spi的工作機制,jdk的spi通過ServiceLoader這個類來完成對應(yīng)接口實現(xiàn)類的加載工作,就拿咱們要說的數(shù)據(jù)庫驅(qū)動來說,
ServiceLoader會在spring項目的classpath中尋找那些滿足下面條件的類:
1、這些jar包的META-INF/services有一個java.sql.Driver的文件
對應(yīng)java.sql.Driver文件中為該數(shù)據(jù)庫驅(qū)動對應(yīng)的數(shù)據(jù)庫驅(qū)動的實現(xiàn)類,比如mysql驅(qū)動對應(yīng)的就是com.mysql.cj.jdbc.Driver,如下圖所示:
JDK這部分有關(guān)SPI具體的實現(xiàn)機制可以閱讀下ServiceLoader的內(nèi)部類LazyIterator,該類的hasNextService、nextService兩個方法就是具體SPI機制工作底層機制。
好了,上面簡要概述了下JDK的SPI工作機制,下面繼續(xù)看spring框架如何使用spi機制來完成數(shù)據(jù)庫驅(qū)動的自動管理的(加載、注銷),接下來就按照事情發(fā)展的先后的先后順序把mysql驅(qū)動加載的全過程屢一下,筆者使用的是springboot 2.x,數(shù)據(jù)源使用的數(shù)據(jù)源為Hikari,這是后來居上的一款數(shù)據(jù)源,憑借其優(yōu)秀的性能以及監(jiān)控機制成為了springboot 2.x之后首推的數(shù)據(jù)源,
用過springboot的小伙伴對springboot的自動裝載機制,數(shù)據(jù)源的配置也是使用的自動裝配機制,
具體類DataSourceAutoConfiguration
注意上面標紅部分,這里面引入的Hikari、Tomcat等(除了DataSourceJmxConfiguration之外)都是一些數(shù)據(jù)源配置,我們先看下
springboot推薦的Hikari數(shù)據(jù)源配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** ** 這是一個Configuration類,該類定義了創(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ù)源配置來創(chuàng)建Hikari數(shù)據(jù)源 HikariDataSource dataSource = createDataSource(properties, HikariDataSource. class ); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } } |
由于在DataSourceAutoConfiguration類中首先引入的就是Hikari的配置,DataSource沒有創(chuàng)建,滿足ConditionalOnMissingBean以及其他一些條件,就會使用該配置類創(chuàng)建數(shù)據(jù)源,好了接下來看下createDataSource到底是怎么創(chuàng)建數(shù)據(jù)源的,
這個過程又是怎么跟SPI關(guān)聯(lián)起來的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
abstract class DataSourceConfiguration { @SuppressWarnings ( "unchecked" ) protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) { //使用DataSourceProperties數(shù)據(jù)源配置創(chuàng)建DataSourceBuilder對象(設(shè)計模式中的建造者模式) 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方法中會調(diào)用屬性的設(shè)置,反射機制,在設(shè)置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對應(yīng)的類,即com.mysql.cj.jdbc.Driver類,該類為mysql對應(yīng)的驅(qū)動類 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對象,接下來看下com.mysql.cj.jdbc.Driver創(chuàng)建對象過程中發(fā)生了什么 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 { //調(diào)用DriverManager注冊自身,DriverManager使用CopyOnWriteArrayList來存儲已加載的數(shù)據(jù)庫驅(qū)動,然后當創(chuàng)建連接時最終會調(diào)用DriverManager的getConnection方法,這才是真正面向數(shù)據(jù)庫的,只不過spring的jdbc幫助我們屏蔽了這些細節(jié) java.sql.DriverManager.registerDriver( new Driver()); } catch (SQLException E) { throw new RuntimeException( "Can't register driver!" ); } } |
上面已經(jīng)來到了DriverManager類,那么DriverManager類里面是否有什么秘密呢,繼續(xù)往下走,
看下DriverManager的重要方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
static { //靜態(tài)方法,jvm第一次加載該類時會調(diào)用該代碼塊 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機制提供的驅(qū)動,本文用到了兩個,一個是mysql的,一個是oracle的,注意該方法只會在jvm第一次加載DriverManager類時才會調(diào)用,所以會一次性加載所有的數(shù)據(jù)庫驅(qū)動 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ù)庫驅(qū)動加載的地方,對應(yīng)ServiceLoader類的LazyIterator類,所以看下該類的hasNext一級next方法即可,上面已經(jīng)講過,這里就不再贅述 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機制來加載數(shù)據(jù)庫驅(qū)動的,至于DriverManager的getConnection方法調(diào)用過程可以使用類似的方式分析下,在DriverManager的getConnection方法打個斷點,當代碼停在斷點處時,通過Idea或者eclipse的堆棧信息就可以看出個大概了。
但愿本文能幫助一些人了解mysql驅(qū)動加載的整個過程,加深對SPI機制的理解。希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://jonhuster.blog.csdn.net/article/details/104394196