- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章使用springboot通过spi机制加载mysql驱动的过程由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
SPI是一种JDK提供的加载插件的灵活机制,分离了接口与实现,就拿常用的数据库驱动来说,我们只需要在spring系统中引入对应的数据库依赖包(比如mysql-connector-java以及针对oracle的ojdbc6驱动),然后在yml或者properties配置文件中对应的数据源配置就可自动使用对应的sql驱动, 。
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这个类来完成对应接口实现类的加载工作,就拿咱们要说的数据库驱动来说, 。
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的自动装载机制,数据源的配置也是使用的自动装配机制, 。
注意上面标红部分,这里面引入的Hikari、Tomcat等(除了DataSourceJmxConfiguration之外)都是一些数据源配置,我们先看下 。
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到底是怎么创建数据源的, 。
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类里面是否有什么秘密呢,继续往下走, 。
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驱动的过程的文章就讲到这里了,如果你想了解更多关于使用springboot通过spi机制加载mysql驱动的过程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
如果我声明了类似的类型 type test(NSIZE) integer, len :: NSIZE real :: dummy(NSIZE) contains procedure,
我知道这是一个不太可能的事情,但是由于“选项私有(private)模块”的限制,甚至更糟糕的“私有(private)子/函数”的限制,有谁知道是否有一种方法可以从 Excel 应用程序隐藏 VBA 过
我有两个表,property 和 component。 component.id_property = property.id。 我正在尝试创建一个过程,该过程对所选属性的组件进行计数,如果所选属性没
我有一份报告,它是在 SSRS 2005 中开发的,我正在使用存储过程从数据库中获取结果。报告输出的结果非常简单,如下图所示。 如果假设我正在寻找不同的成员 例如:- MemberID c108 c
我需要一个通用函数/过程,该函数/过程将根据提供的数据计算出我的淡入淡出时间和值,如下所示: 我将字节值保存在字节数组中:这些是起始值。然后,我在其他数组中存储了一些值:这些将是新值。然后我有时间要提
我想在界面的多个按钮上创建相同的操作。是否只能通过创建单独的操作监听器方法并调用执行操作的方法才可行,还是还有其他方法?是否可以将按钮放在一个组中并执行以下操作:- groupButton.setOn
我有以下情况: procedure Test; begin repeat TryAgain := FALSE; try // Code // Code if this an
我正在尝试执行以下操作;假设我在 Oracle 中创建了一个对象类型 create type test as object( name varchar2(12), member procedure p
问题: 如果可能的话,如何声明一个用于任何类型参数的函数 T其中 T 的唯一约束是它被定义为 1D array如 type T is array ( integer range <> ) of a_r
我正在尝试创建这个 mysql 过程来制作一个包含今年所有日期和所有时间的表(以一小时为间隔。) CREATE TABLE FECHAS ( created_at datetime ); CREA
所以, 我在这里面临一个问题,这让我发疯,我认为这是一个愚蠢的错误,所以我不是 MySQL 的新手,但它并不像我想象的那样工作。 尝试将此语句部署到 MySQL 后,我收到此错误: ERROR 106
我有一个架构,其中包含星球大战中的人物列表、他们出现的电影、他们访问的行星等。这是架构: CREATE DATABASE IF NOT EXISTS `starwarsFINAL` /*!40100
我一直在为一家慈善机构创建一款应用程序,允许家庭在节日期间注册接收礼物。数据库组织有多个表。下面列出了这些表(及其架构/创建语句): CREATE TABLE IF NOT EXISTS ValidD
正如上面标题所解释的,我正在尝试编写一个sql函数来按日期删除表而不删除系统表。我在此消息下方放置了一张图片,以便直观地解释我的问题。任何帮助将不胜感激!感谢您的时间! 最佳答案 您可以通过查询INF
DELIMITER $$ CREATE PROCEDURE INSERT_NONE_HISTORY_CHECKBOX() BEGIN DECLARE note_id bigint(20); F
是否可以编写一个存储过程或触发器,在特定时间在数据库内部自动执行,而无需来自应用程序的任何调用?如果是,那么任何人都可以给我一个例子或链接到一些我可以阅读如何做到这一点的资源。 最佳答案 查看 pgA
我需要创建一个过程:1)从表中的字段中选择一些文本并将其存储在变量中2) 更新相同的记录字段,仅添加 yyyymmdd 格式的日期以及过程中的附加文本输入...类似这样的... delimiter /
好的,这就是我想做的: 如果条目已存在(例如基于字段name),则只需返回其id 如果没有,请添加 这是我迄今为止所管理的(对于“如果不存在,则创建它”部分): INSERT INTO `object
以下是我编写的程序,用于找出每位客户每天购买的前 10 件商品。 这是我尝试过的第一个 PL/SQL 操作。它没有达到我预期的效果。 我使用的逻辑是接受开始日期、结束日期以及我对每个客户感兴趣的前“x
我正在尝试在MySQL中创建一个过程那insert week s(当年)发送至我的 week table 。但存在一个问题,因为在为下一行添加第一行后,我收到错误: number column can
我是一名优秀的程序员,十分优秀!