好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

比如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一样,你不引用它,它是不会自动加载到jvm的,不是引入了下面的的两个sql驱动依赖就必然会加载oracle以及mysql的驱动:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

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

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

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

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

具体类DataSourceAutoConfiguration

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

springboot推荐的Hikari数据源配置:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/**

  ** 这是一个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关联起来的

?

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数据源配置创建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的重要方法:

?

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 {

     //静态方法,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机制的理解。希望能给大家一个参考,也希望大家多多支持。

原文链接:https://jonhuster.blog.csdn.net/article/details/104394196

查看更多关于使用springboot通过spi机制加载mysql驱动的过程的详细内容...

  阅读:17次