激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - 使用springboot通過spi機制加載mysql驅(qū)動的過程

使用springboot通過spi機制加載mysql驅(qū)動的過程

2021-11-01 10:49PolarisHuster Java教程

這篇文章主要介紹了使用springboot通過spi機制加載mysql驅(qū)動的過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

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,如下圖所示:

使用springboot通過spi機制加載mysql驅(qū)動的過程

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

使用springboot通過spi機制加載mysql驅(qū)動的過程

注意上面標紅部分,這里面引入的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

延伸 · 閱讀

精彩推薦
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發(fā)現(xiàn)了對于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7482021-02-04
  • Java教程xml與Java對象的轉(zhuǎn)換詳解

    xml與Java對象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發(fā)項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經(jīng)有好久沒有升過級了。升級完畢重啟之后,突然發(fā)現(xiàn)好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現(xiàn)搶紅包功能

    Java實現(xiàn)搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現(xiàn)搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
主站蜘蛛池模板: 国产18成人免费视频 | 免费久久久久久 | 欧美一级美国一级 | 色视频一区二区 | 国产又粗又爽又深的免费视频 | 亚洲一区二区在线免费 | 国产免费看片 | 欧美日韩高清在线观看 | 欧美视频不卡 | 羞羞网站在线观看入口免费 | 亚洲亚色 | 精品欧美一区二区精品久久小说 | 亚洲午夜久久久久 | 一区二区三区手机在线观看 | 日本在线播放一区二区 | 国产日韩在线视频 | 国产精品视频免费网站 | chinesehdxxxx实拍 日韩电影视频 | 国内自拍网址 | 国产一级在线看 | 日韩大片在线永久观看视频网站免费 | 免费专区 - 91爱爱 | 法国极品成人h版 | 在线 日本 制服 中文 欧美 | 国产一区二区三区视频免费 | 狠狠操电影 | 午夜精品在线播放 | 视频一区二区三区在线观看 | 国产日韩一区二区三区在线观看 | 欧美一级黄色免费看 | 欧美综合在线观看视频 | 毛片视| 久久九九热re6这里有精品 | 国产成人av一区 | 欧美中文字幕一区二区三区亚洲 | 久久最新免费视频 | 欧美3p激情一区二区三区猛视频 | 大西瓜永久免费av在线 | 热re91久久精品国产99热 | 国产 日韩 亚洲 欧美 | 亚洲成人久久精品 |