- 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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
广播的原则 如果两个数组的后缘维度(从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为它们是广播兼容的。广播会在缺失维度和(或)轴长度为1的维度上进行。 在上面的对arr每一列减去列
之前在讲 MySQL 事务隔离性提到过,对于写操作给读操作的影响这种情形下发生的脏读、不可重复读、虚读问题。是通过MVCC 机制来进行解决的,那么MVCC到底是如何实现的,其内部原理是怎样的呢?我们要
我创建了一个 JavaScript 对象来保存用户在 ColorBox 中检查复选框时设置的值。 . 我对 jQuery 和“以正确的方式”编程 JavaScript 比较陌生,希望确保以下用于捕获用
我为了回答aquestion posted here on SO而玩示例,发现很难理解python的import *破坏作用域的机制。 首先是一点上下文:这个问题不涉及实际问题;我很清楚from fo
我想让我的类具有标识此类的参数 ID。例如我想要这样的东西: class Car { public static virtual string ID{get{return "car";}} }
更新:我使用的是 Java 1.6.34,没有机会升级到 Java 7。 我有一个场景,我每分钟只能调用一个方法 80 次。它实际上是由第 3 方编写的服务 API,如果您多次调用它,它会“关闭”(忽
希望这对于那些使用 Javascript 的人来说是一个简单的答案...... 我有一个日志文件,该文件正在被一个脚本监视,该脚本将注销中的新行提供给任何连接的浏览器。一些人评论说,他们希望看到的更多
我们正在开发针对 5.2 开发的 PHP 应用程序,但我们最近迁移到了 PHP 5.3。我们没有时间去解决所有迁移到 PHP 5.3 的问题。具体来说,我们有很多消息: Declaration of
简介 在实现定时调度功能的时候,我们往往会借助于第三方类库来完成,比如: quartz 、 spring schedule 等等。jdk从1.3版本开始,就提供了基于 timer 的定时调度功能。
Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端。这就需要有一种可以在两端传输数据的协议。Java序列化机制就是为了解决这个问题而
我将编写自己的自定义控件,它与 UIButton 有很大不同。由于差异太大,我决定从头开始编写。所以我所有的子类都是 UIControl。 当我的控件在内部被触摸时,我想以目标操作的方式触发一条消息。
在我的代码中,在创建 TIdIMAP4 连接之前,我设置了一大堆 SASL 机制,希望按照规定的“最好到最差”顺序,如下所示: IMAP.SASLMechanisms.Add.SASL := mIdS
在 Kubernetes 中,假设我们有 3 个 pod,它们物理上托管在节点 X、Y 和 Z 上。当我使用“kubectl expose”将它们公开为服务时,它们都是集群中的节点(除了 X、Y 和
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 9 年前。 Improve this ques
我知道进程间通信 (ipc) 有几种方法,例如: 文件 信号 socket 消息队列 管道 命名管道 信号量 共享内存 消息传递 内存映射文件 但是我无法找到将这些机制相互比较并指出它们在不同环境中的
当我尝试连接到 teradata 时,出现了TD2 机制不支持单点登录 错误。 在 C# 中,我遇到了类似的问题,我通过添加 connectionStringBuilder.Authetication
我有一个带有 JSON API 的简单 Javascript 应用程序。目前它在客户端运行,但我想将它从客户端移动到服务器。我习惯于学习新平台,但在这种情况下,我的时间非常有限 - 所以我需要找到绝对
我想了解事件绑定(bind)/解除绑定(bind)在浏览器中是如何工作的。具体来说,如果我删除一个已经绑定(bind)了事件的元素,例如使用 jQuery:$("#anElement").remove
我不是在寻找具体答案,只是一个想法或提示。我有以下问题: Android 应用程序是 Web 服务的客户端。它有一个线程,通过 http 协议(protocol)发送事件(带有请求 ID 的 XML
我正在研究 FreeBSD TCP/IP 栈。似乎有 2 种 syn flood 机制,syncookies 和 syncache。我的问题是关于 syncookies,它是从头开始还是在 SYN 队
我是一名优秀的程序员,十分优秀!